Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add GUI #2375

Open
wants to merge 234 commits into
base: dev/1.4
Choose a base branch
from
Open

Add GUI #2375

wants to merge 234 commits into from

Conversation

singlecoder
Copy link
Member

@singlecoder singlecoder commented Sep 12, 2024

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced new sprite rendering interfaces and classes (e.g., ISpriteRenderer, SimpleSpriteAssembler, SlicedSpriteAssembler, TiledSpriteAssembler).
    • Added UI components including UICanvas, UIGroup, UIRenderer, and UITransform.
    • Enhanced pointer event handling with UIPointerEventEmitter and PointerEventData.
    • Added support for various UI transitions (ColorTransition, ScaleTransition, SpriteTransition).
    • Added enums for canvas rendering modes and resolution adaptation strategies.
  • Improvements

    • Updated rendering logic for UI elements and sprites for better performance and flexibility.
    • Enhanced text rendering capabilities with the new Label class and associated utilities.
    • Improved project export functionality for HTML5 projects from the Galacean Editor.
  • Bug Fixes

    • Resolved various issues related to pointer event handling and UI interactions.
  • Documentation

    • Updated README and various documentation files to reflect changes in platform support and project export features.
    • Removed outdated mini program documentation and focused on HTML5 project guidance.

const { elements: projectE } = virtualCamera.projectionMatrix;
const { elements: viewE } = virtualCamera.viewMatrix;
(projectE[0] = 2 / canvas.width), (projectE[5] = 2 / canvas.height), (projectE[10] = 0);
rhi.activeRenderTarget(null, new Vector4(0, 0, 1, 1), renderContext.flipProjection, 0);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will create Vector4 every frame

Comment on lines +62 to +66
(row[0] = expectWidth * left * widthScale), (row[1] = row[2] = fixedLeft * widthScale);
row[3] = width - expectWidth * (1 - right) * widthScale;
} else {
row = [expectWidth * left, fixedLeft, width - fixedRight, width - expectWidth * (1 - right)];
(row[0] = expectWidth * left), (row[1] = fixedLeft), (row[2] = width - fixedRight);
row[3] = width - expectWidth * (1 - right);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can not agree more

import { IUIElement } from "./interface/IUIElement";

@dependentComponents(UITransform, DependentMode.AutoAdd)
export class UIRenderer extends Renderer implements IUIElement {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should add implements ISpriteRenderer, as follows:

export class UIRenderer extends Renderer implements IUIElement, ISpriteRenderer {}

const texture = this.sprite?.texture;
if (!texture) {
return false;
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be more appropriate to check the texture first

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Outside diff range and nitpick comments (4)
packages/core/src/2d/text/TextUtils.ts (2)

274-274: LGTM with documentation suggestion

The method signature change is correct. Consider adding JSDoc to document the new parameter type.

+/**
+ * Measures text without wrapping for both TextRenderer and UIText.
+ * @param renderer - The text renderer (TextRenderer or UIText)
+ * @returns TextMetrics containing measurement information
+ */
 static measureTextWithoutWrap(renderer: TextRenderer | UIText): TextMetrics {

Line range hint 100-274: Consider adding integration tests for UIText measurements

Since these methods now support both TextRenderer and UIText, it would be valuable to add integration tests that verify:

  1. Text measurement consistency between TextRenderer and UIText
  2. Edge cases with different text styles and overflow modes
  3. Performance implications of supporting both types

This will help ensure compatibility and prevent regressions.

packages/core/src/ui/UIText.ts (2)

323-323: Address the TODO: Optimize world positions buffer update

There's a TODO comment at line 323 indicating that optimization is needed: //@todo: Always update world positions to buffer, should opt. Would you like assistance in implementing this optimization or opening a GitHub issue to track it?


514-514: Consider splitting assignments for better readability

At line 514, the line combines multiple assignments in one statement:

const charRenderInfo = (charRenderInfos[renderElementCount++] = charRenderInfoPool.get());

This reduces code clarity. Consider splitting the assignments.

Suggested change:

- const charRenderInfo = (charRenderInfos[renderElementCount++] = charRenderInfoPool.get());
+ const charRenderInfo = charRenderInfoPool.get();
+ charRenderInfos[renderElementCount++] = charRenderInfo;
🧰 Tools
🪛 Biome

[error] 514-514: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 53d136e and 15b8e6c.

📒 Files selected for processing (3)
  • packages/core/src/2d/text/TextUtils.ts (3 hunks)
  • packages/core/src/ui/UIText.ts (1 hunks)
  • packages/core/src/ui/index.ts (1 hunks)
🧰 Additional context used
🪛 Biome
packages/core/src/ui/UIText.ts

[error] 274-274: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 513-513: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 514-514: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 524-524: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 526-526: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 608-608: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 610-610: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

🪛 GitHub Check: lint
packages/core/src/ui/index.ts

[failure] 10-10:
Delete

🔇 Additional comments (2)
packages/core/src/2d/text/TextUtils.ts (2)

3-3: LGTM: Import addition is correct

The UIText import is properly placed and supports the new type parameter additions.


100-100: Verify type compatibility between TextRenderer and UIText

The method now accepts both TextRenderer and UIText types. Please ensure that UIText implements all required properties and methods:

  • _getSubFont()
  • width
  • height
  • lineSpacing
  • overflowMode
✅ Verification successful

Let me gather more information about the UIText class implementation to verify the required properties and methods.


Let me verify one last aspect - the inheritance hierarchy and interface implementation of UIText and TextRenderer.


Based on the gathered evidence, I can now provide the final response:

Type compatibility between TextRenderer and UIText is verified

The verification confirms that UIText implements all required properties and methods:

  • _getSubFont() method is properly implemented
  • Required properties are present: _width, _height, _lineSpacing, _overflowMode
  • Both classes extend from base rendering classes (UIRenderer and Renderer respectively) and share similar structure
  • Both classes have compatible implementations for text rendering functionality
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify UIText implements required properties and methods

# Search for UIText class definition and implementation
ast-grep --pattern 'class UIText {
  $$$
  _getSubFont() {
    $$$
  }
  $$$
}'

# Search for width, height, lineSpacing, and overflowMode properties
rg -A 5 'class UIText' 

Length of output: 599


Script:

#!/bin/bash
# Search for required properties and methods in UIText class
rg -A 2 "(_getSubFont|width\s*:|height\s*:|lineSpacing\s*:|overflowMode\s*:)" packages/core/src/ui/UIText.ts

# Search for property declarations
ast-grep --pattern 'class UIText extends UIRenderer {
  $$$
  private _width: $_
  $$$
}'

# Check if UIText extends TextRenderer or has similar interface
rg -A 2 "interface.*Text" packages/core/src/2d/text/

Length of output: 1562


Script:

#!/bin/bash
# Check TextRenderer class and its properties
rg -A 5 "class TextRenderer" packages/core/src/2d/text/

# Check if UIText implements or extends any text-related interfaces
rg "class UIText.*implements" packages/core/src/ui/UIText.ts

# Look for any shared interfaces between TextRenderer and UIText
ast-grep --pattern 'interface $_ {
  $$$
  _getSubFont(): $_
  $$$
}'

Length of output: 825

Comment on lines 256 to 257
//@ts-ignore
this._color._onValueChanged = this._onColorChange.bind(this);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid using //@ts-ignore by properly typing properties

At lines 256-257, the use of //@ts-ignore suppresses TypeScript errors, which might hide potential issues. Consider properly typing the _onValueChanged property in the Color class to avoid the need for //@ts-ignore.

const { r, g, b, a } = this.color;
const tempIndices = CharRenderInfo.triangles;
const tempIndicesLength = tempIndices.length;
const subChunk = (textChunk.subChunk = this._getChunkManager().allocateSubChunk(count * 4));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid nested assignments within expressions

At line 608, the assignments are nested within an expression:

const subChunk = (textChunk.subChunk = this._getChunkManager().allocateSubChunk(count * 4));

This can reduce code readability. Consider separating the assignments.

Suggested change:

- const subChunk = (textChunk.subChunk = this._getChunkManager().allocateSubChunk(count * 4));
+ textChunk.subChunk = this._getChunkManager().allocateSubChunk(count * 4);
+ const subChunk = textChunk.subChunk;
🧰 Tools
🪛 Biome

[error] 608-608: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

const tempIndicesLength = tempIndices.length;
const subChunk = (textChunk.subChunk = this._getChunkManager().allocateSubChunk(count * 4));
const vertices = subChunk.chunk.vertices;
const indices = (subChunk.indices = []);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid nested assignments within expressions

At line 610, the assignment is nested within an expression:

const indices = (subChunk.indices = []);

This can be confusing. Consider assigning to subChunk.indices first, then referencing it.

Suggested change:

- const indices = (subChunk.indices = []);
+ subChunk.indices = [];
+ const indices = subChunk.indices;
🧰 Tools
🪛 Biome

[error] 610-610: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

const char = line[j];
const charInfo = charFont._getCharInfo(char);
if (charInfo.h > 0) {
firstRow < 0 && (firstRow = j);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid assignments within expressions to improve code clarity

At line 513, the assignment firstRow = j is performed within a logical AND expression. This can be confusing and reduce readability. It's better to use an explicit if statement.

Suggested change:

- firstRow < 0 && (firstRow = j);
+ if (firstRow < 0) {
+   firstRow = j;
+ }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
firstRow < 0 && (firstRow = j);
if (firstRow < 0) {
firstRow = j;
}
🧰 Tools
🪛 Biome

[error] 513-513: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

localPositions.set(left, top, right, bottom);
i === firstLine && (maxY = Math.max(maxY, top));
minY = Math.min(minY, bottom);
j === firstRow && (minX = Math.min(minX, left));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid assignments within expressions for better code readability

At line 526, the assignment minX = Math.min(minX, left) is inside a logical AND expression. For clarity, use an explicit if statement.

Suggested change:

- j === firstRow && (minX = Math.min(minX, left));
+ if (j === firstRow) {
+   minX = Math.min(minX, left);
+ }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
j === firstRow && (minX = Math.min(minX, left));
if (j === firstRow) {
minX = Math.min(minX, left);
}
🧰 Tools
🪛 Biome

[error] 526-526: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

const top = startY + ascent;
const bottom = startY - descent;
localPositions.set(left, top, right, bottom);
i === firstLine && (maxY = Math.max(maxY, top));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid assignments within expressions for code clarity

At line 524, the assignment maxY = Math.max(maxY, top) is performed within a logical AND expression. This can make the code less readable. Use an explicit if statement instead.

Suggested change:

- i === firstLine && (maxY = Math.max(maxY, top));
+ if (i === firstLine) {
+   maxY = Math.max(maxY, top);
+ }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
i === firstLine && (maxY = Math.max(maxY, top));
if (i === firstLine) {
maxY = Math.max(maxY, top);
}
🧰 Tools
🪛 Biome

[error] 524-524: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

this._freeTextChunks();
this._textChunks = null;

this._subFont && (this._subFont = null);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid assignments within expressions for better readability

At line 274, the assignment this._subFont = null is used within a logical AND expression. This can make the code harder to read and maintain. Consider using an explicit if statement for clarity.

Suggested change:

- this._subFont && (this._subFont = null);
+ if (this._subFont) {
+   this._subFont = null;
+ }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
this._subFont && (this._subFont = null);
if (this._subFont) {
this._subFont = null;
}
🧰 Tools
🪛 Biome

[error] 274-274: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

* feat: add button
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 28

🧹 Outside diff range and nitpick comments (46)
packages/core/src/ui/interactive/transition/TransitionType.ts (1)

1-6: Consider adding JSDoc comments to document each transition type.

Adding documentation would improve maintainability by explaining the purpose and behavior of each transition type.

+/**
+ * Defines the types of transitions available for UI elements.
+ */
 export enum TransitionType {
+  /** No transition effect */
   None,
+  /** Color-based transition effect */
   Color,
+  /** Sprite-based transition effect */
   Sprite,
+  /** Scale-based transition effect */
   Scale
 }
packages/core/src/ui/interactive/InteractiveState.ts (2)

1-7: Consider renaming 'Disable' to 'Disabled' for grammatical consistency.

The enum implementation looks good and covers all essential UI interaction states. However, 'Disable' should be 'Disabled' to maintain consistent grammar (adjective form) with other state names.

 export enum InteractiveState {
   None,
   Normal,
   Pressed,
   Hover,
-  Disable
+  Disabled
 }

1-1: Add JSDoc documentation for the enum.

Since this enum is a core part of the GUI infrastructure used across multiple components, please add JSDoc documentation explaining:

  • The purpose of each state
  • Usage examples
  • State transition expectations
+/**
+ * Represents the interactive states of UI components.
+ * 
+ * @remarks
+ * - None: Initial/unset state
+ * - Normal: Default interactive state
+ * - Pressed: Component is being actively pressed/clicked
+ * - Hover: Mouse is hovering over the component
+ * - Disabled: Component is non-interactive
+ */
 export enum InteractiveState {
packages/core/src/ui/interface/IGroupElement.ts (2)

4-8: Add JSDoc documentation and consider using readonly modifiers.

The interface definition could benefit from documentation explaining its purpose and usage. Additionally, consider marking properties that shouldn't change after initialization as readonly.

Here's the suggested improvement:

+/**
+ * Interface for elements that can be part of a UIGroup.
+ * Extends ICanvasElement to provide group-specific functionality.
+ */
 export interface IGroupElement extends ICanvasElement {
-  _group: UIGroup;
-  _indexInGroup: number;
+  readonly _group: UIGroup;
+  readonly _indexInGroup: number;
   _onGroupModify(flag: GroupModifyFlags): void;
 }

4-8: Consider encapsulating internal implementation details.

The interface exposes implementation details through underscore-prefixed properties and methods. This could make it harder to maintain and refactor the codebase in the future.

Consider:

  1. Moving internal details to a separate internal interface
  2. Using a more public-facing API design
  3. Using TypeScript's private fields (#) if these are truly meant to be private

Example approach:

export interface IGroupElement extends ICanvasElement {
  readonly group: UIGroup;
  readonly indexInGroup: number;
  onModify(flag: GroupModifyFlags): void;
}

// Internal interface for implementation
interface IGroupElementInternal extends IGroupElement {
  _group: UIGroup;
  _indexInGroup: number;
  _onGroupModify(flag: GroupModifyFlags): void;
}
packages/core/src/ui/interface/IUIGraphics.ts (1)

5-10: Consider interface segregation for better modularity.

The current interface combines multiple responsibilities (depth management, hit testing, and group behavior). Consider splitting this into more focused interfaces:

interface IDepthManagement {
  depth: number;
}

interface IRaycastable {
  raycastEnable: boolean;
  raycastPadding: Vector4;
  raycast(ray: Ray, out: HitResult, distance: number): boolean;
}

export interface IUIGraphics extends IGroupElement, IDepthManagement, IRaycastable {}

This would:

  • Make it easier to implement each behavior independently
  • Allow for better composition of UI elements
  • Improve testability of each concern

Would you like me to create a GitHub issue to track this refactoring suggestion?

packages/core/src/ui/index.ts (2)

1-15: LGTM! Well-organized exports structure.

The exports are logically grouped and follow a clear pattern, providing a good foundation for the GUI infrastructure. The separation between core enums, UI components, and transitions is clear and maintainable.

Consider adding JSDoc comments to document the main export groups:

+/** Core UI enums */
 export { UIGroup } from "./UIGroup";
 export { CanvasRenderMode } from "./enums/CanvasRenderMode";
 export { ResolutionAdaptationStrategy } from "./enums/ResolutionAdaptationStrategy";

+/** UI Components */
 export { Button } from "./Button";
 export { UICanvas } from "./UICanvas";
 export { UIImage } from "./UIImage";
 export { UIRenderer } from "./UIRenderer";
 export { UIText } from "./UIText";
 export { UITransform } from "./UITransform";

+/** Transition Components */
 export { ColorTransition } from "./interactive/transition/ColorTransition";
 export { ScaleTransition } from "./interactive/transition/ScaleTransition";
 export { SpriteTransition } from "./interactive/transition/SpriteTransition";
 export { Transition } from "./interactive/transition/Transition";

5-15: Consider sorting exports within groups.

While the current organization is clear, consider sorting the exports alphabetically within their groups to make it easier to locate specific exports as the list grows.

 export { Button } from "./Button";
-export { UICanvas } from "./UICanvas";
-export { UIImage } from "./UIImage";
-export { UIRenderer } from "./UIRenderer";
-export { UIText } from "./UIText";
-export { UITransform } from "./UITransform";
+export { UICanvas } from "./UICanvas";
+export { UIImage } from "./UIImage";
+export { UIRenderer } from "./UIRenderer";
+export { UIText } from "./UIText";
+export { UITransform } from "./UITransform";

-export { ColorTransition } from "./interactive/transition/ColorTransition";
-export { ScaleTransition } from "./interactive/transition/ScaleTransition";
-export { SpriteTransition } from "./interactive/transition/SpriteTransition";
-export { Transition } from "./interactive/transition/Transition";
+export { ColorTransition } from "./interactive/transition/ColorTransition";
+export { ScaleTransition } from "./interactive/transition/ScaleTransition";
+export { SpriteTransition } from "./interactive/transition/SpriteTransition";
+export { Transition } from "./interactive/transition/Transition";
packages/core/src/ui/interactive/transition/ScaleTransition.ts (2)

5-12: Consider enhancing configurability and visual feedback.

The current implementation could benefit from:

  1. Constructor parameters to allow customization of scale values and duration
  2. Different scale values for hover and disabled states to provide better visual feedback

Consider this implementation:

-  constructor() {
+  constructor(config?: {
+    normal?: number;
+    hover?: number;
+    pressed?: number;
+    disabled?: number;
+    duration?: number;
+  }) {
     super();
-    this._normal = 1;
-    this._hover = 1;
-    this._pressed = 1.2;
-    this._disabled = 1;
-    this._duration = 0.1;
+    this._normal = config?.normal ?? 1;
+    this._hover = config?.hover ?? 1.1;
+    this._pressed = config?.pressed ?? 1.2;
+    this._disabled = config?.disabled ?? 0.9;
+    this._duration = config?.duration ?? 0.2;
   }

18-20: Consider using easing functions for smoother transitions.

The current linear interpolation might not provide the most visually pleasing transition. Consider implementing easing functions for smoother animations.

Example implementation:

protected override _updateCurrentValue(srcValue: number, destValue: number, weight: number): void {
  // Ease in-out quad function
  const easeWeight = weight < 0.5
    ? 2 * weight * weight
    : 1 - Math.pow(-2 * weight + 2, 2) / 2;
  this._currentValue = (destValue - srcValue) * easeWeight + srcValue;
}
packages/core/src/ui/interactive/transition/ColorTransition.ts (2)

6-15: Consider documenting color states and extracting constants.

The color states and transition duration would benefit from documentation and constant definitions.

Consider applying these improvements:

+/** Default transition duration in seconds */
+private static readonly DEFAULT_DURATION = 0.1;
+
+/** Default color states for different interaction states */
+private static readonly COLORS = {
+  NORMAL: new Color(1, 1, 1, 1),
+  HOVER: new Color(245 / 255, 245 / 255, 245 / 255, 1),
+  PRESSED: new Color(200 / 255, 200 / 255, 200 / 255, 1),
+  DISABLED: new Color(200 / 255, 200 / 255, 200 / 255, 1)
+} as const;

 constructor() {
   super();
-  this._normal = new Color(1, 1, 1, 1);
-  this._hover = new Color(245 / 255, 245 / 255, 245 / 255, 1);
-  this._pressed = new Color(200 / 255, 200 / 255, 200 / 255, 1);
-  this._disabled = new Color(200 / 255, 200 / 255, 200 / 255, 1);
-  this._duration = 0.1;
+  this._normal = ColorTransition.COLORS.NORMAL.clone();
+  this._hover = ColorTransition.COLORS.HOVER.clone();
+  this._pressed = ColorTransition.COLORS.PRESSED.clone();
+  this._disabled = ColorTransition.COLORS.DISABLED.clone();
+  this._duration = ColorTransition.DEFAULT_DURATION;
   this._currentValue = new Color();
 }

17-29: Consider optimizing color object reuse.

The current implementation reuses the color object effectively in _getTargetValueCopy, but _updateCurrentValue could be optimized further.

Consider this optimization:

 protected override _updateCurrentValue(srcValue: Color, destValue: Color, weight: number): void {
   if (weight >= 1) {
     this._currentValue.copyFrom(destValue);
   } else {
-    Color.lerp(srcValue, destValue, weight, this._currentValue);
+    // Avoid potential allocation in Color.lerp by ensuring _currentValue is always used
+    this._currentValue.r = srcValue.r + (destValue.r - srcValue.r) * weight;
+    this._currentValue.g = srcValue.g + (destValue.g - srcValue.g) * weight;
+    this._currentValue.b = srcValue.b + (destValue.b - srcValue.b) * weight;
+    this._currentValue.a = srcValue.a + (destValue.a - srcValue.a) * weight;
   }
 }
packages/core/src/ui/Button.ts (3)

6-6: Consider adding JSDoc for the listeners property.

While the property is well-typed and properly encapsulated, adding JSDoc would improve maintainability by documenting the purpose of this collection.

+  /** Collection of click event listeners managed using SafeLoopArray for safe iteration. */
   private _listeners: SafeLoopArray<IUIListener> = new SafeLoopArray<IUIListener>();

8-14: Consider preventing duplicate listener registrations.

The current implementation allows the same listener to be registered multiple times, which could lead to redundant executions.

   addClicked(listener: (event: PointerEventData) => void): void {
+    if (this._listeners.find((value) => value.fn === listener)) {
+      return;
+    }
     this._listeners.push({ fn: listener });
   }

38-41: Consider adding JSDoc to document the interface.

While the interface is well-structured, adding documentation would improve maintainability.

+/**
+ * Interface for UI event listeners with destruction tracking.
+ */
 export interface IUIListener {
+  /** The event handling function */
   fn: (event: PointerEventData) => void;
+  /** Flag indicating if the listener has been destroyed */
   destroyed?: boolean;
 }
packages/core/src/input/pointer/emitter/PointerPhysicsEventEmitter.ts (3)

13-49: Consider breaking down the complex raycast processing logic.

The method contains nested loops and multiple conditions that could be extracted into smaller, focused methods for better maintainability.

Consider refactoring into smaller methods:

private _isValidScene(scene: Scene): boolean {
  return scene.isActive && !scene.destroyed;
}

private _isPointerInViewport(x: number, y: number, viewport: { x: number, y: number, width: number, height: number }): boolean {
  return !(x < viewport.x || y < viewport.y || 
          x > viewport.x + viewport.width || 
          y > viewport.y + viewport.height);
}

private _processCamera(camera: Camera, ray: Ray, pointer: Pointer): boolean {
  if (camera.renderTarget) return false;
  
  const { x, y } = pointer.position;
  if (!this._isPointerInViewport(x, y, camera.pixelViewport)) {
    return false;
  }
  
  camera.screenPointToRay(pointer.position, ray);
  return true;
}

79-96: Consider caching event data to reduce object creation.

The method creates multiple event data objects with the same parameters. Consider caching the event data for better performance.

private _processUp(pointer: Pointer): void {
  const { _enteredEntity: enteredEntity, _draggedEntity: draggedEntity } = this;
  
  if (enteredEntity) {
    const sameTarget = this._pressedEntity === enteredEntity;
    const eventData = this._createEventData(pointer, enteredEntity, enteredEntity);
    
    this._invokeEntityScripts(enteredEntity, (script: Script) => {
      script.onPointerUp?.(eventData);
      sameTarget && script.onPointerClick?.(eventData);
      script.onPointerDrop?.(eventData);
    });
  }
  // ... rest of the method
}

98-115: Apply similar event data caching optimization.

Similar to the _processUp method, consider caching the event data objects to reduce object creation.

packages/core/src/ui/UIUtils.ts (4)

21-37: Add JSDoc documentation for better maintainability.

This method handles complex parent traversal and listener management. Consider adding JSDoc documentation to explain:

  • The purpose of the method
  • The relationship between entities and listeners
  • The significance of the root canvas check

47-82: Consider extracting shared logic between registration methods.

The registerElementToCanvas and registerElementToGroup methods share similar patterns for managing indices and elements. Consider extracting the common logic into a private helper method to improve maintainability and reduce code duplication.

Example approach:

private static registerElementToContainer<T extends ICanvasElement | IGroupElement, C extends UICanvas | UIGroup>(
  element: T,
  container: C,
  getPreContainer: (el: T) => C,
  getElements: (c: C) => DisorderedArray<T>,
  updateIndex: (el: T, index: number) => void
): void {
  const preContainer = getPreContainer(element);
  if (preContainer !== container) {
    // ... shared logic here
  }
}
🧰 Tools
🪛 Biome

[error] 53-53: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 72-72: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


84-126: Consider using type guard functions for safer type casting.

Replace type casting with type guard functions to improve type safety and readability:

private static isUICanvas(component: Component): component is UICanvas {
  return component._componentType === ComponentType.UICanvas;
}

private static isUIGroup(component: Component): component is UIGroup {
  return component._componentType === ComponentType.UIGroup;
}

Then use them in the methods:

if (component.enabled && this.isUICanvas(component) && component._isRootCanvas) {
  return component;
}

136-143: Add comments explaining matrix transformations.

The matrix operations for projection and view transformations would benefit from documentation explaining:

  • The purpose of each transformation
  • The coordinate space being used
  • The significance of the magic numbers (2, 0)
packages/core/src/ui/UIRenderer.ts (2)

59-61: Consider omitting redundant type annotations.

The type annotations can be inferred from the initializers and can be omitted for better conciseness.

Apply these changes:

-protected _alpha: number = 1;
+protected _alpha = 1;

-protected _color: Color = new Color(1, 1, 1, 1);
+protected _color = new Color(1, 1, 1, 1);

229-241: Improve variable naming in _hitTest method.

Consider using more descriptive variable names for better readability.

Apply these changes:

 protected _hitTest(localPosition: Vector3): boolean {
   const { x, y } = localPosition;
   const uiTransform = <UITransform>this._transform;
-  const { x: width, y: height } = uiTransform.size;
-  const { x: pivotX, y: pivotY } = uiTransform.pivot;
+  const { x: elementWidth, y: elementHeight } = uiTransform.size;
+  const { x: pivotOffsetX, y: pivotOffsetY } = uiTransform.pivot;
   const { x: paddingLeft, y: paddingBottom, z: paddingRight, w: paddingTop } = this.raycastPadding;
   
   return (
-    x >= -width * pivotX + paddingLeft &&
-    x <= width * (1 - pivotX) - paddingRight &&
-    y >= -height * pivotY + paddingTop &&
-    y <= height * (1 - pivotY) - paddingBottom
+    x >= -elementWidth * pivotOffsetX + paddingLeft &&
+    x <= elementWidth * (1 - pivotOffsetX) - paddingRight &&
+    y >= -elementHeight * pivotOffsetY + paddingTop &&
+    y <= elementHeight * (1 - pivotOffsetY) - paddingBottom
   );
 }
packages/core/src/input/pointer/emitter/PointerUIEventEmitter.ts (3)

223-232: Improve readability and maintainability of path composition

Several improvements can be made to the _composedPath method:

  1. Avoid assignment within expressions
  2. Add early return for invalid input
  3. Consider extracting the path composition logic to a separate method for better reusability

Apply this diff to improve the code:

 private _composedPath(element: Component, path: Entity[]): Entity[] {
   if (!element) {
     path.length = 0;
     return path;
   }
-  let entity = (path[0] = element._entity);
+  path[0] = element._entity;
+  let entity = element._entity;
   let i = 1;
   if (element._componentType === ComponentType.UICanvas && (<UICanvas>element)._isRootCanvas) {
     path.length = 1;
     return path;
   } else {
     const rootEntity = (<UICanvas | UIRenderer>element)._rootCanvas._entity;
     for (; i < PointerUIEventEmitter._MAX_PATH_DEPTH && !!entity && entity !== rootEntity; i++) {
       entity = path[i] = entity.parent;
     }
   }
   path.length = i;
   return path;
 }
🧰 Tools
🪛 Biome

[error] 223-223: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


170-171: Consider performance optimizations for path operations

The current implementation creates new paths and performs comparisons frequently, which could impact performance with high-frequency pointer events. Consider:

  1. Caching paths when possible
  2. Using a more efficient data structure for path comparison
  3. Implementing path pooling to reduce garbage collection

13-21: Enhance documentation and type safety

The class could benefit from:

  1. JSDoc documentation for the class explaining its purpose and usage
  2. Type annotations for event handlers
  3. Documentation of the relationship between different pointer states
  4. Interface definitions for the event data structure
packages/core/src/input/pointer/PointerManager.ts (5)

4-12: Well-structured architectural improvements!

The introduction of ClearableObjectPool and event emitters shows good separation of concerns and memory management practices. The modular approach with separate emitter classes will make the code more maintainable and testable.

Also applies to: 41-41


99-110: Consider caching physics initialization state

The _engine._physicsInitialized check on each pointer creation could lead to inconsistent behavior if physics initialization state changes during runtime. Consider caching this state during PointerManager initialization.

 constructor(engine: Engine, target: EventTarget) {
   // ... existing code ...
+  this._physicsEnabled = engine._physicsInitialized;
 }

 // Later in the code
-_engine._physicsInitialized && pointer._addEmitters(PointerPhysicsEventEmitter, _eventPool);
+this._physicsEnabled && pointer._addEmitters(PointerPhysicsEventEmitter, _eventPool);

224-255: Consider using event type constants

The string literals for event types could be replaced with constants to prevent typos and improve maintainability. Consider creating an enum or constant object for event types.

const PointerEventTypes = {
  DOWN: 'pointerdown',
  UP: 'pointerup',
  MOVE: 'pointermove',
  LEAVE: 'pointerleave',
  CANCEL: 'pointercancel'
} as const;

282-288: Optimize cleanup operations

Consider combining the array clearing operations for better performance. You could create a helper method to clear multiple arrays at once.

private _clearArrays() {
  [this._nativeEvents, this._pointers, this._downList, 
   this._downMap, this._upList, this._upMap].forEach(arr => arr.length = 0);
}

41-41: Document object pool configuration

Consider adding JSDoc comments to explain the object pool's configuration and lifecycle, particularly its garbage collection strategy and pool size management.

packages/core/src/ui/UIImage.ts (2)

196-216: Optimize property access and null checks.

Multiple property accesses and null checks can be optimized by destructuring and combining conditions.

 protected override _render(context: RenderContext): void {
-  const { _sprite: sprite } = this;
-  const transform = this._transform as UITransform;
-  const { x: width, y: height } = transform.size;
-  if (!sprite?.texture || !width || !height) {
+  const { _sprite: sprite, _transform: transform, _color: color, _alpha: alpha } = this;
+  if (!sprite?.texture || !transform) {
     return;
   }
+  const { x: width, y: height } = (transform as UITransform).size;
+  if (!width || !height || color.a * alpha <= 0) {
+    return;
+  }

   let material = this.getMaterial();
   if (!material) {
     return;
   }
   // @todo: This question needs to be raised rather than hidden.
   if (material.destroyed) {
     material = this._engine._basicResources.uiDefaultMaterial;
   }

-  if (this._color.a * this._alpha <= 0) {
-    return;
-  }

362-371: Consider using TypeScript const enum for better performance.

The ImageUpdateFlags enum would benefit from being a const enum as it's used internally for bitwise operations.

-enum ImageUpdateFlags {
+const enum ImageUpdateFlags {
   UV = 0x20,

   /** LocalPosition | WorldPosition | UV */
   AllPositionAndUV = 0x23,
   /** LocalPosition | WorldPosition | UV | Color */
   AllPositionUVAndColor = 0x33,
   /** LocalPosition | WorldPosition | UV | Color | LocalBounds | WorldBounds */
   All = 0x3f
 }
packages/core/src/ui/UIText.ts (3)

235-241: Consider adding error handling in the constructor.

The constructor assumes engine._textDefaultFont and engine._basicResources.textDefaultMaterial will always exist. Consider adding null checks and throwing appropriate errors if these dependencies are missing.

 constructor(entity: Entity) {
   super(entity);

   const { engine } = this;
+  if (!engine._textDefaultFont) {
+    throw new Error("Default font not initialized in engine.");
+  }
   this.font = engine._textDefaultFont;
+  if (!engine._basicResources?.textDefaultMaterial) {
+    throw new Error("Default text material not initialized in engine resources.");
+  }
   this.setMaterial(engine._basicResources.textDefaultMaterial);
 }

257-257: Improve readability by avoiding assignment in expression.

The assignment within the logical AND expression makes the code harder to read.

- this._subFont && (this._subFont = null);
+ if (this._subFont) {
+   this._subFont = null;
+ }
🧰 Tools
🪛 Biome

[error] 257-257: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


324-363: Consider performance optimizations in render loop.

The render method performs several operations that could be optimized:

  1. Multiple property accesses could be cached
  2. Array iterations could be optimized
  3. Object allocations in render loop should be minimized

Consider these optimizations:

  1. Cache frequently accessed properties outside the loop
  2. Use pre-allocated arrays for temporary calculations
  3. Consider implementing object pooling for SubRenderElements
  4. Profile the rendering performance with large text content to identify bottlenecks

Would you like me to provide a detailed implementation of these optimizations?

packages/core/src/Entity.ts (1)

108-113: Consider adding type safety for transform access.

While the refactoring from public property to private property with getter is good for encapsulation, consider adding runtime type checking to prevent potential issues with transform types.

 get transform(): Transform {
+  if (!this._transform) {
+    throw new Error('Transform component is missing');
+  }
   return this._transform;
 }
packages/core/src/ui/interactive/transition/Transition.ts (2)

6-6: Avoid using 'any' as default type for generics to maintain type safety

The generic type parameters T and K default to any, which reduces the benefits of TypeScript's type checking. By specifying more precise default types or removing the defaults, you can enhance type safety and catch potential errors at compile time.


114-119: Ensure _countDown does not become negative in _onUpdate

In the _onUpdate() method, _countDown is decremented by delta, but there's no check to prevent it from becoming negative. Negative _countDown values could cause unintended behavior in the transition logic.

Consider clamping _countDown to zero after decrementing to prevent negative values.

Suggested change:

if (this._countDown > 0) {
- this._countDown -= delta;
+ this._countDown = Math.max(0, this._countDown - delta);
  this._updateValue();
}
packages/core/src/ui/interactive/UIInteractive.ts (1)

115-115: Separate assignments for better readability

Assigning multiple variables in a single statement can reduce readability. Consider splitting the assignments to improve clarity.

Apply this diff:

-        this._isPointerInside = this._isPointerDown = false;
+        this._isPointerInside = false;
+        this._isPointerDown = false;
packages/core/src/ui/UICanvas.ts (5)

89-89: Use if statement instead of logical operators for clarity

Using logical operators like && for control flow can reduce code readability. Consider using an explicit if statement to make the code clearer.

Apply this diff to enhance readability:

- (referenceResolution.x !== val.x || referenceResolution.y !== val.y) && referenceResolution.copyFrom(val);
+ if (referenceResolution.x !== val.x || referenceResolution.y !== val.y) {
+   referenceResolution.copyFrom(val);
+ }

142-143: Use if statement instead of logical operators for side effects

Using logical operators like && to trigger side effects can make the code harder to read and understand. Replace it with an if statement for better clarity.

Apply this diff to improve code clarity:

- this._realRenderMode === CanvasRenderMode.ScreenSpaceOverlay &&
-   this.scene._componentsManager._overlayCanvasesSortingFlag;
+ if (this._realRenderMode === CanvasRenderMode.ScreenSpaceOverlay) {
+   this.scene._componentsManager._overlayCanvasesSortingFlag = true;
+ }

154-154: Use if statement instead of logical operators for function calls

Using logical operators like && to conditionally call functions can reduce code readability. Consider using an explicit if statement for better clarity.

Apply this diff to enhance readability:

- this._realRenderMode === CanvasRenderMode.ScreenSpaceCamera && this._adapterPoseInScreenSpace();
+ if (this._realRenderMode === CanvasRenderMode.ScreenSpaceCamera) {
+   this._adapterPoseInScreenSpace();
+ }

194-194: Avoid assignment within an expression for better readability

Assigning values within expressions can be confusing and reduce code clarity. Separate the assignment from the declaration to improve readability.

Apply this diff to enhance code clarity:

- const renderElement = (this._renderElement = engine._renderElementPool.get());
+ this._renderElement = engine._renderElementPool.get();
+ const renderElement = this._renderElement;
🧰 Tools
🪛 Biome

[error] 194-194: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


359-359: Avoid assignment within a logical expression

Assigning a value within a logical expression can reduce code readability. Use an explicit if statement instead.

Apply this diff to improve clarity:

- child.isActive && (depth = this._walk(child, elements, depth));
+ if (child.isActive) {
+   depth = this._walk(child, elements, depth);
+ }
🧰 Tools
🪛 Biome

[error] 359-359: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 15b8e6c and 4b2487d.

📒 Files selected for processing (22)
  • packages/core/src/Entity.ts (11 hunks)
  • packages/core/src/input/pointer/PointerManager.ts (6 hunks)
  • packages/core/src/input/pointer/emitter/PointerPhysicsEventEmitter.ts (1 hunks)
  • packages/core/src/input/pointer/emitter/PointerUIEventEmitter.ts (1 hunks)
  • packages/core/src/ui/Button.ts (1 hunks)
  • packages/core/src/ui/UICanvas.ts (1 hunks)
  • packages/core/src/ui/UIGroup.ts (1 hunks)
  • packages/core/src/ui/UIImage.ts (1 hunks)
  • packages/core/src/ui/UIRenderer.ts (1 hunks)
  • packages/core/src/ui/UIText.ts (1 hunks)
  • packages/core/src/ui/UIUtils.ts (1 hunks)
  • packages/core/src/ui/index.ts (1 hunks)
  • packages/core/src/ui/interactive/InteractiveState.ts (1 hunks)
  • packages/core/src/ui/interactive/UIInteractive.ts (1 hunks)
  • packages/core/src/ui/interactive/transition/ColorTransition.ts (1 hunks)
  • packages/core/src/ui/interactive/transition/ScaleTransition.ts (1 hunks)
  • packages/core/src/ui/interactive/transition/SpriteTransition.ts (1 hunks)
  • packages/core/src/ui/interactive/transition/Transition.ts (1 hunks)
  • packages/core/src/ui/interactive/transition/TransitionType.ts (1 hunks)
  • packages/core/src/ui/interface/ICanvasElement.ts (1 hunks)
  • packages/core/src/ui/interface/IGroupElement.ts (1 hunks)
  • packages/core/src/ui/interface/IUIGraphics.ts (1 hunks)
🧰 Additional context used
🪛 Biome
packages/core/src/Entity.ts

[error] 616-616: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/input/pointer/emitter/PointerPhysicsEventEmitter.ts

[error] 67-67: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 67-67: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/input/pointer/emitter/PointerUIEventEmitter.ts

[error] 85-85: The function should not return a value because its return type is void.

The function is here:

'void' signals the absence of value. The returned value is likely to be ignored by the caller.

(lint/correctness/noVoidTypeReturn)


[error] 102-102: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 102-102: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 223-223: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/ui/Button.ts

[error] 21-21: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 34-34: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/ui/UICanvas.ts

[error] 194-194: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 211-211: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 359-359: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 473-474: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 483-484: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 485-487: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

packages/core/src/ui/UIGroup.ts

[error] 105-105: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)


[error] 110-110: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)


[error] 153-153: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/ui/UIRenderer.ts

[error] 163-163: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 167-167: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 168-168: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 166-170: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

packages/core/src/ui/UIText.ts

[error] 257-257: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 496-496: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 497-497: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 507-507: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 509-509: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 591-591: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 593-593: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/ui/UIUtils.ts

[error] 16-153: Avoid classes that contain only static members.

Prefer using simple functions instead of classes with only static members.

(lint/complexity/noStaticOnlyClass)


[error] 53-53: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 72-72: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 130-130: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 130-130: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 131-131: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 131-131: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 132-132: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 132-132: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)

packages/core/src/ui/interactive/UIInteractive.ts

[error] 147-149: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

🔇 Additional comments (35)
packages/core/src/ui/interactive/transition/TransitionType.ts (1)

1-6: LGTM! Clean and well-structured enum implementation.

The enum provides a clear and type-safe way to represent different transition types for UI elements.

packages/core/src/ui/interface/IGroupElement.ts (1)

1-1: Verify the GroupModifyFlags type definition.

The GroupModifyFlags import is used but its type definition isn't visible in the current context.

✅ Verification successful

GroupModifyFlags is properly defined and used correctly

The GroupModifyFlags enum is defined in packages/core/src/ui/UIGroup.ts with clear flag values:

  • None = 0x0
  • Alpha = 0x1
  • Interactive = 0x2
  • All = 0x3

The import in IGroupElement.ts correctly references this enum which is used throughout the UI system for group modifications.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check the definition of GroupModifyFlags
# Expected: Find the type definition and its possible values

# Search for GroupModifyFlags definition
echo "Searching for GroupModifyFlags definition:"
ast-grep --pattern 'enum GroupModifyFlags {
  $$$
}'

# Backup search using ripgrep
echo -e "\nBackup search for GroupModifyFlags:"
rg -A 5 "GroupModifyFlags"

Length of output: 7403

packages/core/src/ui/interface/ICanvasElement.ts (1)

1-2: LGTM! Clean and focused imports.

The imports are well-organized and specifically import only the required types.

packages/core/src/ui/interface/IUIGraphics.ts (1)

1-3: LGTM! Well-organized imports.

The imports are properly structured with clear separation between external package and internal module imports.

packages/core/src/ui/interactive/transition/SpriteTransition.ts (3)

1-5: LGTM! Clean and well-structured class declaration.

The imports are appropriate, and the class extension with generic types is well-defined.


10-12: Verify if binary transition is the intended behavior.

The current implementation provides an abrupt switch between source and destination sprites at weight = 1, rather than a smooth transition. If this is intentional, consider adding a comment explaining why interpolation is not used.

✅ Verification successful

Binary transition for sprites is consistent with the codebase design

The implementation is correct as sprites cannot be interpolated like other properties. This is evident from the codebase where:

  • ColorTransition uses Color.lerp for smooth color transitions
  • ScaleTransition uses linear interpolation for smooth scaling
  • SpriteTransition uses binary switching, which is appropriate as sprites are discrete assets

Consider adding a comment explaining this design choice to improve code clarity.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other transition implementations to verify if this binary approach is consistent
ast-grep --pattern 'class $_Transition extends Transition<$_, $_> {
  $$$
  protected override _updateCurrentValue($_, $_, weight: number): void {
    $$$
  }
  $$$
}'

Length of output: 169


Script:

#!/bin/bash
# Let's try a different approach to find transition implementations
rg "_updateCurrentValue.*weight.*number" -A 5

# Also search for the base Transition class to understand the expected behavior
rg "class Transition[^a-zA-Z]" -A 10

Length of output: 3776


14-16: Add type safety and error handling for _normal property.

The method uses this._normal as a fallback, but this property's initialization is not visible in the current context.

Consider adding:

  1. Type guard to ensure value is a valid Sprite
  2. Explicit initialization of _normal in the constructor
  3. Error handling for invalid sprite values
  protected override _applyValue(value: Sprite): void {
+   if (value && !this.isValidSprite(value)) {
+     throw new Error('Invalid sprite value provided');
+   }
    this._target.sprite = value || this._normal;
  }
+ 
+ private isValidSprite(value: any): value is Sprite {
+   return value && typeof value === 'object';
+   // Add additional sprite validation logic
+ }
packages/core/src/ui/index.ts (1)

1-15: Verify all exports are properly implemented.

Let's ensure all exported components exist and are properly implemented.

✅ Verification successful

Let me generate another script to verify the specific exports since the previous output didn't show us the actual export declarations.


Based on the verification results, I can now provide the final response:

All exports are properly implemented and available

All exported components from packages/core/src/ui/index.ts are verified to exist and are properly implemented:

  • Enums: CanvasRenderMode and ResolutionAdaptationStrategy are properly exported
  • UI Components: UIGroup, Button, UICanvas, UIImage, UIText, and UITransform are all exported as classes
  • Transitions: ColorTransition, ScaleTransition, SpriteTransition, and base Transition are all exported as classes
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that all exported files exist and export the expected components

# Test: Check if all imported files exist
echo "Checking if all files exist..."
for file in "UIGroup" "enums/CanvasRenderMode" "enums/ResolutionAdaptationStrategy" \
           "Button" "UICanvas" "UIImage" "UIRenderer" "UIText" "UITransform" \
           "interactive/transition/ColorTransition" "interactive/transition/ScaleTransition" \
           "interactive/transition/SpriteTransition" "interactive/transition/Transition"; do
  if ! fd -p "packages/core/src/ui/$file.ts"; then
    echo "Missing file: $file"
  fi
done

# Test: Verify exports in each file
echo "Checking exports in each file..."
for file in $(fd -e ts . "packages/core/src/ui"); do
  echo "Checking $file..."
  ast-grep --pattern 'export class $name' "$file"
  ast-grep --pattern 'export enum $name' "$file"
  ast-grep --pattern 'export interface $name' "$file"
done

Length of output: 14751


Script:

#!/bin/bash
# Check specific exports in each file

# Check UIGroup export
echo "Checking UIGroup..."
rg "export (class|enum|interface) UIGroup" packages/core/src/ui/UIGroup.ts -A 1

echo "Checking enums..."
rg "export enum (CanvasRenderMode|ResolutionAdaptationStrategy)" packages/core/src/ui/enums/ -A 1

echo "Checking UI components..."
for component in "Button" "UICanvas" "UIImage" "UIRenderer" "UIText" "UITransform"; do
  echo "Checking $component..."
  rg "export (class|interface) $component" "packages/core/src/ui/$component.ts" -A 1
done

echo "Checking transitions..."
for transition in "ColorTransition" "ScaleTransition" "SpriteTransition" "Transition"; do
  echo "Checking $transition..."
  rg "export (class|interface) $transition" "packages/core/src/ui/interactive/transition/$transition.ts" -A 1
done

Length of output: 3977

packages/core/src/ui/interactive/transition/ScaleTransition.ts (2)

1-4: LGTM! Clean and type-safe class declaration.

The imports are minimal and the generic type parameters are well-defined.


14-16: Verify uniform scaling assumption.

The method assumes uniform scaling by only checking the x-axis scale. This might lead to unexpected behavior if the entity has non-uniform scaling.

packages/core/src/ui/interactive/transition/ColorTransition.ts (1)

1-5: LGTM! Clean imports and proper class declaration.

The class structure and type parameters are well-defined, following TypeScript best practices.

packages/core/src/ui/Button.ts (2)

1-5: LGTM! Clean imports and proper inheritance.

The imports are focused and the inheritance hierarchy is appropriate for a button component.


24-30: LGTM! Efficient and safe event dispatch implementation.

The implementation correctly handles listener iteration and destroyed state checking.

packages/core/src/input/pointer/emitter/PointerPhysicsEventEmitter.ts (5)

1-11: LGTM! Clean class structure with well-defined properties.

The class is well-organized with appropriate imports and clearly named protected properties for tracking entity states.


54-61: LGTM! Clean and focused drag event handling.

The method is well-structured with proper null checks and clear event handling logic.


66-74: Multiple assignments in expression reduce readability.

This issue was previously identified. The suggestion to separate the assignments for better readability is still valid.

🧰 Tools
🪛 Biome

[error] 67-67: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 67-67: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


117-119: LGTM! Thorough cleanup of entity references.

The method properly cleans up all entity references in a single statement.


121-142: LGTM! Well-designed helper methods.

The private methods are focused, properly encapsulated, and maintain consistent event handling patterns.

packages/core/src/ui/UIGroup.ts (4)

1-41: LGTM! Well-structured class definition with proper property management.

The class structure follows best practices with:

  • Clear separation of concerns using decorators
  • Proper type definitions
  • Well-documented internal properties

43-75: LGTM! Well-implemented property accessors.

The getters and setters properly handle state updates and trigger necessary modifications.


117-144: LGTM! Thorough lifecycle management.

The methods properly handle:

  • Registration and cleanup of entity listeners
  • Memory management with array clearing
  • Garbage collection

186-191: LGTM! Well-defined bit flags enum.

The enum correctly uses bit flags to enable efficient combination of multiple modification types.

packages/core/src/ui/UIRenderer.ts (1)

205-227: 🛠️ Refactor suggestion

Optimize raycast performance and improve readability.

The raycast method can be optimized by:

  1. Caching the plane intersection result
  2. Using early returns for better readability

Apply these changes:

 _raycast(ray: Ray, out: HitResult, distance: number = Number.MAX_SAFE_INTEGER): boolean {
   const plane = UIRenderer._tempPlane;
   const { transform } = this._entity;
   const { worldForward, worldMatrix, worldPosition } = transform;
   const normal = plane.normal.copyFrom(worldForward);
   plane.distance = -Vector3.dot(normal, worldPosition);
   
-  const curDistance = ray.intersectPlane(plane);
-  if (curDistance >= 0 && curDistance < distance) {
+  const intersectionDistance = ray.intersectPlane(plane);
+  if (intersectionDistance < 0 || intersectionDistance >= distance) {
+    return false;
+  }

-    const hitPointWorld = ray.getPoint(curDistance, UIRenderer._tempVec30);
+  const hitPointWorld = ray.getPoint(intersectionDistance, UIRenderer._tempVec30);
   const worldMatrixInv = UIRenderer._tempMat;
-  Matrix.invert(transform.worldMatrix, worldMatrixInv);
+  Matrix.invert(worldMatrix, worldMatrixInv);
   
   const localPosition = UIRenderer._tempVec31;
   Vector3.transformCoordinate(hitPointWorld, worldMatrixInv, localPosition);
   
-  if (this._hitTest(localPosition)) {
-    out.distance = curDistance;
-    out.entity = this._entity;
-    out.component = this;
-    out.normal.copyFrom(normal);
-    out.point.copyFrom(hitPointWorld);
-    return true;
-  }
+  if (!this._hitTest(localPosition)) {
+    return false;
   }
-  return false;
+  
+  out.distance = intersectionDistance;
+  out.entity = this._entity;
+  out.component = this;
+  out.normal.copyFrom(normal);
+  out.point.copyFrom(hitPointWorld);
+  return true;
 }

Likely invalid or redundant comment.

packages/core/src/input/pointer/emitter/PointerUIEventEmitter.ts (2)

85-85: Remove unnecessary return statement

The method is declared as void but contains a return null statement.

🧰 Tools
🪛 Biome

[error] 85-85: The function should not return a value because its return type is void.

The function is here:

'void' signals the absence of value. The returned value is likely to be ignored by the caller.

(lint/correctness/noVoidTypeReturn)


14-16: Consider potential thread safety issues with static arrays

The static arrays _path0 and _path1 are shared across all instances and could lead to race conditions if multiple pointer events are processed concurrently. Consider making these instance-level properties or implementing proper synchronization.

packages/core/src/input/pointer/PointerManager.ts (1)

73-83: Verify pointer disposal completeness

While the pointer cleanup logic is improved, ensure that all resources associated with disposed pointers are properly cleaned up. Consider adding explicit cleanup of any event listeners or cached data.

packages/core/src/ui/UIImage.ts (1)

343-356: ⚠️ Potential issue

Fix incorrect assembler usage in Tiled mode.

The _getUVByLocalPosition method incorrectly uses SlicedSpriteAssembler for the Tiled draw mode.

 case SpriteDrawMode.Tiled:
-  return SlicedSpriteAssembler.getUVByLocalPosition(this, size.x, size.y, pivot, position, out);
+  return TiledSpriteAssembler.getUVByLocalPosition(this, size.x, size.y, pivot, position, out);

Likely invalid or redundant comment.

packages/core/src/Entity.ts (4)

10-17: LGTM: New imports and properties enhance UI and state management capabilities.

The additions properly support the new UI infrastructure and state management features.

Also applies to: 86-104


338-338: LGTM: Parent change handling is properly implemented.

The new _setParentChange method correctly propagates changes through the transform hierarchy and dispatches modification events.

Also applies to: 607-610


531-534: LGTM: Proper cleanup of update flag manager.

The destroy method properly cleans up the update flag manager by removing all listeners and nullifying the reference.


798-798: LGTM: Entity modification flags are well-defined.

The new enum properly categorizes different types of entity modifications, and the dispatch in _setSiblingIndex is correctly implemented.

Also applies to: 817-824

packages/core/src/ui/interactive/UIInteractive.ts (1)

12-196: Overall code implementation looks solid

The UIInteractive class is well-structured, and the interactive state management logic is sound. Methods are appropriately overridden, and the use of transitions enhances the interactivity of UI elements.

🧰 Tools
🪛 Biome

[error] 147-149: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

packages/core/src/ui/UICanvas.ts (3)

473-474: Previous comment on missing break statement still applies

The missing break statement in this switch case was identified earlier and needs to be addressed to prevent unintended fall-through.

🧰 Tools
🪛 Biome

[error] 473-474: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


483-484: Previous comment on missing break statement still applies

The issue of the missing break statement in this switch case remains unresolved. Please refer to the earlier feedback.

🧰 Tools
🪛 Biome

[error] 483-484: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


485-487: Previous comment on missing break statement still applies

Please address the previously noted missing break statements to prevent unintended fall-through in this switch case.

🧰 Tools
🪛 Biome

[error] 485-487: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

Comment on lines 4 to 10
export interface ICanvasElement {
_entity: Entity;
_parents: Entity[];
_rootCanvas: UICanvas;
_indexInCanvas: number;
_onEntityModify(flag: EntityModifyFlags): void;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider revising the interface design to follow TypeScript best practices.

Several architectural and style concerns:

  1. The underscore prefix on all members suggests they are internal implementation details, which shouldn't be part of a public interface contract. Consider:

    • Moving these implementation details to an abstract class
    • Or removing the underscore prefix if these are meant to be public members
  2. The interface appears to expose internal state management (_parents, _indexInCanvas), which could make it harder to change the implementation later.

  3. Missing documentation for the interface and its members. Consider adding JSDoc comments to explain:

    • The purpose of the interface
    • The role of each member
    • The contract requirements for implementing classes

Here's a suggested refactor:

+/**
+ * Represents a UI element that can be rendered on a canvas.
+ * Provides contract for managing canvas element hierarchy and modifications.
+ */
 export interface ICanvasElement {
-  _entity: Entity;
-  _parents: Entity[];
-  _rootCanvas: UICanvas;
-  _indexInCanvas: number;
-  _onEntityModify(flag: EntityModifyFlags): void;
+  /** The entity associated with this canvas element */
+  entity: Entity;
+  /** The parent entities in the hierarchy */
+  parents: Entity[];
+  /** The root canvas this element belongs to */
+  rootCanvas: UICanvas;
+  /** The rendering order index within the canvas */
+  indexInCanvas: number;
+  /** Handles modifications to the associated entity */
+  onEntityModify(flag: EntityModifyFlags): void;
 }

Consider:

  1. Breaking this into smaller interfaces (e.g., separate hierarchy management from entity management)
  2. Using composition over inheritance by providing utility classes for common functionality
  3. Reducing coupling with Entity and UICanvas by introducing abstraction layers
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export interface ICanvasElement {
_entity: Entity;
_parents: Entity[];
_rootCanvas: UICanvas;
_indexInCanvas: number;
_onEntityModify(flag: EntityModifyFlags): void;
}
/**
* Represents a UI element that can be rendered on a canvas.
* Provides contract for managing canvas element hierarchy and modifications.
*/
export interface ICanvasElement {
/** The entity associated with this canvas element */
entity: Entity;
/** The parent entities in the hierarchy */
parents: Entity[];
/** The root canvas this element belongs to */
rootCanvas: UICanvas;
/** The rendering order index within the canvas */
indexInCanvas: number;
/** Handles modifications to the associated entity */
onEntityModify(flag: EntityModifyFlags): void;
}

Comment on lines 5 to 10
export interface IUIGraphics extends IGroupElement {
depth: number;
raycastEnable: boolean;
raycastPadding: Vector4;
_raycast(ray: Ray, out: HitResult, distance: number): boolean;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider revising interface design and adding documentation.

  1. The _raycast method is prefixed with underscore suggesting it's private, which is unconventional for interface contracts. Consider:

    • Removing the underscore if this method should be public
    • Or moving this implementation detail to the concrete class if it should be private
  2. The interface would benefit from JSDoc documentation explaining:

    • The purpose and valid range of the depth property
    • The meaning of each component in raycastPadding (left, top, right, bottom)
    • The return value semantics of _raycast

Here's a suggested improvement:

+/**
+ * Interface for UI graphics elements that support raycasting and depth management.
+ */
 export interface IUIGraphics extends IGroupElement {
+  /** The depth value used for z-ordering. Higher values appear in front. */
   depth: number;
+  /** Whether this element should participate in raycasting operations. */
   raycastEnable: boolean;
+  /** Padding values (left, top, right, bottom) to expand/contract the raycast hit area. */
   raycastPadding: Vector4;
-  _raycast(ray: Ray, out: HitResult, distance: number): boolean;
+  /**
+   * Performs raycasting against this UI element.
+   * @param ray The ray to test against
+   * @param out The hit result information if intersection occurs
+   * @param distance The maximum distance to test
+   * @returns true if the ray intersects with this element
+   */
+  raycast(ray: Ray, out: HitResult, distance: number): boolean;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export interface IUIGraphics extends IGroupElement {
depth: number;
raycastEnable: boolean;
raycastPadding: Vector4;
_raycast(ray: Ray, out: HitResult, distance: number): boolean;
}
/**
* Interface for UI graphics elements that support raycasting and depth management.
*/
export interface IUIGraphics extends IGroupElement {
/** The depth value used for z-ordering. Higher values appear in front. */
depth: number;
/** Whether this element should participate in raycasting operations. */
raycastEnable: boolean;
/** Padding values (left, top, right, bottom) to expand/contract the raycast hit area. */
raycastPadding: Vector4;
/**
* Performs raycasting against this UI element.
* @param ray The ray to test against
* @param out The hit result information if intersection occurs
* @param distance The maximum distance to test
* @returns true if the ray intersects with this element
*/
raycast(ray: Ray, out: HitResult, distance: number): boolean;
}

Comment on lines 6 to 8
protected _getTargetValueCopy(): Sprite {
return this._target?.sprite;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider implementing actual value copying and adding documentation.

The method name suggests it returns a copy, but it's returning a reference to the original sprite. This could lead to unintended modifications of the source sprite.

Consider:

  1. Adding JSDoc documentation explaining the method's purpose
  2. Implementing actual value copying if sprite mutation should be prevented:
+ /**
+  * Returns a copy of the target's sprite value.
+  * @returns A copy of the target sprite or undefined if target is not set
+  */
  protected _getTargetValueCopy(): Sprite {
-   return this._target?.sprite;
+   return this._target?.sprite ? { ...this._target.sprite } : undefined;
  }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 22 to 24
protected override _applyValue(value: number): void {
this._target.entity.transform.setScale(value, value, value);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add validation for scale values.

The method should validate scale values to prevent negative or zero scales which could cause rendering issues.

Suggested implementation:

   protected override _applyValue(value: number): void {
+    if (value <= 0) {
+      console.warn('Scale values should be positive. Clamping to 0.01');
+      value = Math.max(0.01, value);
+    }
     this._target.entity.transform.setScale(value, value, value);
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
protected override _applyValue(value: number): void {
this._target.entity.transform.setScale(value, value, value);
}
protected override _applyValue(value: number): void {
if (value <= 0) {
console.warn('Scale values should be positive. Clamping to 0.01');
value = Math.max(0.01, value);
}
this._target.entity.transform.setScale(value, value, value);
}

Comment on lines 31 to 33
protected override _applyValue(value: Color): void {
this._target.color = value;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add null check for target in _applyValue method.

The _applyValue method should validate that _target exists before attempting to set its color property.

Apply this fix:

 protected override _applyValue(value: Color): void {
+  if (!this._target) {
+    return;
+  }
   this._target.color = value;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
protected override _applyValue(value: Color): void {
this._target.color = value;
}
protected override _applyValue(value: Color): void {
if (!this._target) {
return;
}
this._target.color = value;
}

Comment on lines 23 to 29
set normal(value: T) {
const preNormal = this._normal;
if (preNormal !== value) {
this._normal = value;
this._onStateValueDirty(InteractiveState.Normal);
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use deep comparison in setters to detect changes in complex objects

In the setters for normal, pressed, hover, and disabled, the comparison if (preValue !== value) may not detect changes in complex types like Sprite, Color, or Vector3 since it compares object references rather than their contents. If the properties of these objects change without the reference changing, the update might not be detected.

Consider implementing a deep comparison or using an equals method to accurately detect changes in the values.

Also applies to: 35-41, 47-53, 59-65

Comment on lines 125 to 129
private _updateValue() {
const weight = this._duration ? 1 - this._countDown / this._duration : 1;
this._updateCurrentValue(this._initialValue, this._finalValue, weight);
this._target?.enabled && this._applyValue(this._currentValue);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Clamp weight between 0 and 1 in _updateValue to ensure valid interpolation

In the _updateValue() method, weight can become less than 0 or greater than 1 if _countDown becomes negative or exceeds _duration due to floating-point inaccuracies or unexpected usage. This can lead to invalid interpolation results.

Consider clamping weight between 0 and 1 to maintain valid interpolation.

Suggested fix:

private _updateValue() {
- const weight = this._duration ? 1 - this._countDown / this._duration : 1;
+ const weight = this._duration ? Math.min(1, Math.max(0, 1 - this._countDown / this._duration)) : 1;
  this._updateCurrentValue(this._initialValue, this._finalValue, weight);
  this._target?.enabled && this._applyValue(this._currentValue);
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private _updateValue() {
const weight = this._duration ? 1 - this._countDown / this._duration : 1;
this._updateCurrentValue(this._initialValue, this._finalValue, weight);
this._target?.enabled && this._applyValue(this._currentValue);
}
private _updateValue() {
const weight = this._duration ? Math.min(1, Math.max(0, 1 - this._countDown / this._duration)) : 1;
this._updateCurrentValue(this._initialValue, this._finalValue, weight);
this._target?.enabled && this._applyValue(this._currentValue);
}

Comment on lines 82 to 92
set duration(value: number) {
if (value < 0) value = 0;
const preDuration = this._duration;
if (preDuration !== value) {
this._duration = value;
if (this._countDown > 0) {
this._countDown = value * (1 - this._countDown / preDuration);
this._updateValue();
}
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Prevent division by zero when adjusting _countDown in duration setter

In the duration setter, when adjusting _countDown, there is a potential for division by zero if preDuration is zero. This can result in NaN or Infinity values, causing unexpected behavior in the transition timing.

Consider adding a check to handle the scenario when preDuration is zero to prevent division by zero.

Here's a possible fix:

if (preDuration !== value) {
  this._duration = value;
  if (this._countDown > 0) {
+   if (preDuration > 0) {
      this._countDown = value * (1 - this._countDown / preDuration);
+   } else {
+     this._countDown = value;
+   }
    this._updateValue();
  }
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
set duration(value: number) {
if (value < 0) value = 0;
const preDuration = this._duration;
if (preDuration !== value) {
this._duration = value;
if (this._countDown > 0) {
this._countDown = value * (1 - this._countDown / preDuration);
this._updateValue();
}
}
}
set duration(value: number) {
if (value < 0) value = 0;
const preDuration = this._duration;
if (preDuration !== value) {
this._duration = value;
if (this._countDown > 0) {
if (preDuration > 0) {
this._countDown = value * (1 - this._countDown / preDuration);
} else {
this._countDown = value;
}
this._updateValue();
}
}
}

Comment on lines 146 to 149
case EntityModifyFlags.UICanvasEnableInScene:
case EntityModifyFlags.Parent:
UIUtils.registerElementToCanvas(this, UIUtils.getRootCanvasInParent(this._entity));
UIUtils.registerEntityListener(this);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add missing break statements to prevent unintended fallthrough in switch cases

The switch cases for EntityModifyFlags.UICanvasEnableInScene and EntityModifyFlags.Parent lack break statements, causing unintended fallthrough to the next case. This could lead to unexpected behavior.

Apply this diff to add the missing break statements:

 145        switch (flag) {
 146          case EntityModifyFlags.UICanvasEnableInScene:
 147          case EntityModifyFlags.Parent:
 148            UIUtils.registerElementToCanvas(this, UIUtils.getRootCanvasInParent(this._entity));
 149            UIUtils.registerEntityListener(this);
+150            break;
 151          case EntityModifyFlags.UIGroupEnableInScene:
 152            UIUtils.registerElementToGroup(this, UIUtils.getGroupInParents(this._entity));
 153            break;
 154          default:
 155            break;
 156        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
case EntityModifyFlags.UICanvasEnableInScene:
case EntityModifyFlags.Parent:
UIUtils.registerElementToCanvas(this, UIUtils.getRootCanvasInParent(this._entity));
UIUtils.registerEntityListener(this);
case EntityModifyFlags.UICanvasEnableInScene:
case EntityModifyFlags.Parent:
UIUtils.registerElementToCanvas(this, UIUtils.getRootCanvasInParent(this._entity));
UIUtils.registerEntityListener(this);
break;
🧰 Tools
🪛 Biome

[error] 147-149: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

Comment on lines 211 to 215
const { min, max } = renderer.bounds;
if (min.x > width || max.x < 0 || min.y > height || max.y < 0) {
continue;
}
break;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Wrap variable declarations in switch cases within blocks

Variables declared within switch cases can be accessed by other cases if not properly scoped. To prevent unintended access and improve code safety, wrap the case block in braces {}.

Apply this diff to scope the variable declaration:

        case CanvasRenderMode.ScreenSpaceOverlay:
+       {
          const { min, max } = renderer.bounds;
          if (min.x > width || max.x < 0 || min.y > height || max.y < 0) {
            continue;
          }
          break;
+       }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { min, max } = renderer.bounds;
if (min.x > width || max.x < 0 || min.y > height || max.y < 0) {
continue;
}
break;
{
const { min, max } = renderer.bounds;
if (min.x > width || max.x < 0 || min.y > height || max.y < 0) {
continue;
}
break;
}
🧰 Tools
🪛 Biome

[error] 211-211: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Outside diff range and nitpick comments (27)
packages/core/src/ui/interface/IUIGroupable.ts (2)

8-8: Add method documentation and explicit return type.

The method signature could be improved with the following:

  1. Add JSDoc documentation explaining when this method is called and its purpose
  2. Explicitly specify the return type for better type safety

Here's the suggested improvement:

+  /**
+   * Called when the group this element belongs to is modified.
+   * @param flag - Indicates the type of modification made to the group
+   */
-  _onGroupModify(flag: GroupModifyFlags): void;
+  _onGroupModify(flag: GroupModifyFlags): void;

4-9: Consider providing usage examples and integration guidelines.

As this interface is part of the UI infrastructure, consider:

  1. Adding examples in the documentation showing how to implement this interface
  2. Creating integration tests to verify group behavior
  3. Providing guidelines for when to use this interface vs. direct UI elements

This will help other developers understand when and how to use groupable UI elements effectively.

packages/core/src/ui/interface/IUIElement.ts (1)

4-11: Add JSDoc documentation and readonly modifiers.

The interface would benefit from:

  1. JSDoc documentation explaining its purpose and member usage
  2. Readonly modifiers for references that shouldn't be mutated directly
+/**
+ * Represents a UI element that can be rendered in a canvas hierarchy.
+ * Provides core functionality for UI element management and modification tracking.
+ */
 export interface IUIElement {
+  /** The entity associated with this UI element */
-  _entity: Entity;
+  readonly _entity: Entity;
+  /** Array of parent entities in the hierarchy */
-  _parents: Entity[];
+  readonly _parents: Entity[];
+  /** The root canvas containing this UI element */
-  _rootCanvas: UICanvas;
+  readonly _rootCanvas: UICanvas;
+  /** The index position of this element within its canvas */
   _indexInCanvas: number;

+  /**
+   * Handles modifications to the associated entity
+   * @param flag The type of entity modification
+   */
   _onEntityModify(flag: EntityModifyFlags): void;
 }
packages/core/src/ui/index.ts (2)

5-11: LGTM! UI components hierarchy is well-structured.

The exports follow a logical progression from infrastructure (Canvas, Renderer, Transform) to widgets (Button, Image, Text).

Consider documenting the recommended usage patterns and component hierarchy in a README.md file to help developers understand how these components should be composed together.


1-15: Consider improving file organization with grouping comments.

While the exports are logically grouped, adding separator comments would improve readability.

Consider applying this organization:

+// Core
 export { UIGroup } from "./UIGroup";
+
+// Enums
 export { CanvasRenderMode } from "./enums/CanvasRenderMode";
 export { ResolutionAdaptationStrategy } from "./enums/ResolutionAdaptationStrategy";

+// Infrastructure
 export { UICanvas } from "./UICanvas";
 export { UIRenderer } from "./UIRenderer";
 export { UITransform } from "./UITransform";
+
+// Widgets
 export { Button } from "./Button";
 export { Image } from "./Image";
 export { Text } from "./Text";

+// Transitions
 export { ColorTransition } from "./interactive/transition/ColorTransition";
 export { ScaleTransition } from "./interactive/transition/ScaleTransition";
 export { SpriteTransition } from "./interactive/transition/SpriteTransition";
 export { Transition } from "./interactive/transition/Transition";
packages/core/src/ui/UIUtils.ts (3)

21-37: Add null check for element parameter

The method assumes element is not null but doesn't validate it. Consider adding a guard clause to handle invalid input.

 static registerEntityListener(element: IUIElement): void {
+  if (!element) {
+    return;
+  }
   const parents = element._parents;

84-126: Improve type safety with type guards

Replace type casting with type guard functions to make the code more type-safe and maintainable.

+private static isUICanvas(component: any): component is UICanvas {
+  return component._componentType === ComponentType.UICanvas;
+}
+
+private static isUIGroup(component: any): component is UIGroup {
+  return component._componentType === ComponentType.UIGroup;
+}

 static getRootCanvasInParent(entity: Entity): UICanvas {
   while (entity) {
     const components = entity._components;
     for (let i = 0, n = components.length; i < n; i++) {
       const component = components[i];
-      if (
-        component.enabled &&
-        component._componentType === ComponentType.UICanvas &&
-        (<UICanvas>component)._isRootCanvas
-      ) {
-        return <UICanvas>component;
+      if (component.enabled && this.isUICanvas(component) && component._isRootCanvas) {
+        return component;
       }
     }

136-136: Consider using Matrix methods instead of direct element mutation

Direct mutation of matrix elements makes the code harder to maintain and understand. Consider using Matrix class methods.

-projectE[0] = 2 / canvas.width;
-projectE[5] = 2 / canvas.height;
-projectE[10] = 0;
+virtualCamera.projectionMatrix.setOrtho(
+  0, canvas.width,
+  0, canvas.height,
+  -1, 1
+);
packages/core/src/ui/UIGroup.ts (3)

9-41: Add class-level documentation

Consider adding comprehensive JSDoc documentation for the UIGroup class to describe its purpose, responsibilities, and usage patterns.

Add documentation like this:

/**
 * Manages a group of UI elements with hierarchical properties like alpha and interactivity.
 * Handles propagation of modifications to child elements and groups.
 * 
 * @example
 * ```typescript
 * const uiGroup = new UIGroup(entity);
 * uiGroup.alpha = 0.5; // Sets opacity for all child elements
 * ```
 */
export class UIGroup extends Component {

123-144: Consider decomposing _onDisableInScene for better maintainability

The method handles multiple concerns: entity listener cleanup, element cleanup, and group cleanup. Consider breaking it down into smaller, focused methods.

private _cleanupEntityListeners(): void {
  const entityListeners = this._entityListeners;
  entityListeners.forEach(entity => 
    entity._unRegisterModifyListener(this._onEntityModify)
  );
  entityListeners.length = 0;
}

private _cleanupElements(): void {
  const disorderedElements = this._disorderedElements;
  disorderedElements.forEach(element => 
    UIUtils.registerElementToGroup(element, this._parentGroup)
  );
  disorderedElements.length = 0;
  disorderedElements.garbageCollection();
}

private _cleanupGroups(): void {
  const disorderedGroups = this._disorderedGroups;
  disorderedGroups.forEach(element => 
    element._registryToParentGroup(this._parentGroup)
  );
  disorderedGroups.length = 0;
  disorderedGroups.garbageCollection();
}

override _onDisableInScene(): void {
  this._cleanupEntityListeners();
  this._cleanupElements();
  this._cleanupGroups();
  this._parentGroup = null;
  this._entity._dispatchModify(EntityModifyFlags.UIGroupDisableInScene);
}

186-191: Add documentation for GroupModifyFlags

Consider adding JSDoc comments to document the purpose and usage of each flag.

/**
 * Flags indicating which aspects of a UI group need to be modified.
 */
export enum GroupModifyFlags {
  /** No modifications needed */
  None = 0x0,
  /** Alpha (opacity) value needs to be updated */
  Alpha = 0x1,
  /** Interactive state needs to be updated */
  Interactive = 0x2,
  /** All properties need to be updated */
  All = 0x3
}
packages/core/src/ui/UIRenderer.ts (1)

22-28: Consider implementing object pooling for temporary objects.

Using static temporary objects could lead to memory allocation overhead during frequent UI updates. Consider implementing an object pool to manage these temporary objects more efficiently.

class UIRendererObjectPool {
  private static _vec3Pool: Vector3[] = [];
  private static _matrixPool: Matrix[] = [];
  
  static acquireVector3(): Vector3 {
    return this._vec3Pool.pop() || new Vector3();
  }
  
  static releaseVector3(vec: Vector3): void {
    vec.set(0, 0, 0);
    this._vec3Pool.push(vec);
  }
}
packages/core/src/ui/UICanvas.ts (2)

20-24: Consider type inference for numeric and boolean properties.

TypeScript can automatically infer types from initial values. You can simplify the property declarations by removing explicit type annotations where the type is obvious from the initialization.

Apply this diff to simplify the type declarations:

-  _isRootCanvas: boolean = false;
+  _isRootCanvas = false;
-  _canvasIndex: number = -1;
+  _canvasIndex = -1;
-  _sortDistance: number = 0;
+  _sortDistance = 0;
-  private _sortOrder: number = 0;
+  private _sortOrder = 0;
-  private _distance: number = 10;
+  private _distance = 10;
-  private _referenceResolution: Vector2 = new Vector2(800, 600);
+  private _referenceResolution = new Vector2(800, 600);

Also applies to: 27-27, 45-45, 64-64, 66-66, 70-70


284-284: Rename methods to use correct terminology.

The methods use "adapter" instead of "adapt" in their names, which is grammatically incorrect.

Apply this diff to fix the method names:

-  private _adapterPoseInScreenSpace(): void {
+  private _adaptPoseInScreenSpace(): void {
-  private _adapterSizeInScreenSpace(): void {
+  private _adaptSizeInScreenSpace(): void {

Update all references to these methods accordingly.

Also applies to: 303-303

packages/core/src/2d/text/TextRenderer.ts (2)

Line range hint 87-611: Consider batching dirty flag updates.

The current implementation sets dirty flags individually for each property change. Consider implementing a batch update mechanism for scenarios where multiple properties are changed simultaneously.

+ private _batchUpdate(callback: () => void): void {
+   const prevDirtyFlag = this._dirtyUpdateFlag;
+   callback();
+   if (this._dirtyUpdateFlag !== prevDirtyFlag) {
+     this._onDirtyFlagChanged(this._dirtyUpdateFlag);
+   }
+ }

  set text(value: string) {
    value = value || "";
    if (this._text !== value) {
+     this._batchUpdate(() => {
        this._text = value;
        this._setDirtyFlagTrue(RendererUpdateFlags.AllPositionAndBounds);
+     });
    }
  }

689-699: Add JSDoc comments for individual flags.

The TextRendererUpdateFlags enum would benefit from detailed documentation for each flag, explaining when they are set and their implications.

 enum TextRendererUpdateFlags {
+  /** Flag indicating that the SubFont needs to be updated */
   SubFont = 0x10,
+  /** Flag indicating that the Color needs to be updated */
   Color = 0x20,

+  /** 
+   * Combined flag for font-related updates that affect position and bounds
+   * @remarks This includes SubFont and all position/bounds flags
+   */
   FontAllPositionAndBounds = 0x1f,
+  /** 
+   * Combined flag for all possible updates
+   * @remarks This includes all font, position, bounds, and color flags
+   */
   All = 0x3f
 }
packages/core/src/Entity.ts (1)

404-404: Consider adding a type check for UITransform inheritance.

The current implementation assumes UITransform is a direct class, but it might be inherited.

-    this._transform instanceof UITransform && child.addComponent(UITransform);
+    (this._transform instanceof UITransform || this._transform.constructor === UITransform) && child.addComponent(UITransform);
packages/core/src/ui/interactive/transition/Transition.ts (3)

1-155: Consider adding a stop method to halt the transition.

It might be useful to have a way to immediately stop the transition and set the value to the final state. This can be helpful in scenarios where you want to cancel the transition midway and snap to the end state.

Here's a possible implementation:

stop() {
  this._countDown = 0;
  this._initialValue = this._finalValue = this._getValueByState(this._finalState);
  this._updateValue();
}

1-155: Consider adding an onTransitionEnd callback.

It could be beneficial to provide a way for users to register a callback that gets invoked when the transition reaches its end state. This can be useful for triggering additional actions or state changes based on the completion of the transition.

Here's a possible implementation:

private _onTransitionEnd: () => void;

set onTransitionEnd(callback: () => void) {
  this._onTransitionEnd = callback;
}

private _updateValue() {
  // ...
  if (this._countDown <= 0 && this._onTransitionEnd) {
    this._onTransitionEnd();
  }
}

1-155: Consider adding support for easing functions.

Currently, the transition uses linear interpolation between the initial and final values. Introducing support for easing functions can provide more control over the transition's behavior and allow for more visually appealing animations.

Here's a possible implementation:

type EasingFunction = (t: number) => number;

private _easingFunction: EasingFunction = (t) => t;

set easingFunction(fn: EasingFunction) {
  this._easingFunction = fn;
}

private _updateValue() {
  const t = this._duration ? 1 - this._countDown / this._duration : 1;
  const weight = this._easingFunction(t);
  // ...
}

You can then provide a set of built-in easing functions or allow users to define their own.

packages/core/src/input/pointer/emitter/PointerUIEventEmitter.ts (1)

229-231: Consider defining the magic number as a constant for better maintainability.

The magic number 1024 is used to prevent infinite loops by limiting the iteration count. Defining this number as a constant with a descriptive name would enhance code readability and make future adjustments easier.

Apply this diff to define the magic number as a constant:

-      for (; i < 1024 && !!entity && entity !== rootEntity; i++) {
+      const MAX_PATH_DEPTH = 1024;
+      for (; i < MAX_PATH_DEPTH && !!entity && entity !== rootEntity; i++) {
         entity = path[i] = entity.parent;
       }
packages/core/src/ui/Image.ts (1)

208-209: Address the TODO comment regarding material handling

There's a TODO comment indicating that an issue needs to be raised rather than hidden when the material is destroyed. This should be resolved to ensure proper error handling and developer awareness.

Do you want assistance in implementing the error handling mechanism or opening a GitHub issue to track this task?

packages/core/src/ui/Text.ts (5)

225-225: Avoid assignments within expressions.

The static analysis tool has flagged several instances where assignments are used within expressions. While this is syntactically valid, it can lead to confusion and potential bugs.

Consider separating the assignments from the expressions to improve readability and maintainability. For example:

- firstRow < 0 && (firstRow = j);
+ if (firstRow < 0) {
+   firstRow = j;
+ }

This applies to the assignments on lines 225, 466, 467, 477, 479, 565, and 567.

Also applies to: 466-466, 467-467, 477-477, 479-479, 565-565, 567-567

🧰 Tools
🪛 Biome

[error] 225-225: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


293-331: Consider extracting the rendering logic into a separate method.

The _render method is quite lengthy and contains multiple responsibilities, such as updating positions, colors, and managing render elements. To improve readability and maintainability, consider extracting some of the rendering logic into separate methods.

For example, you could extract the code that creates and sets up the SubRenderElement instances into a separate method, like _createSubRenderElements. This would make the _render method more focused and easier to understand.


402-541: Optimize the _updateLocalData method.

The _updateLocalData method is a critical part of the text rendering process, and its performance can impact the overall rendering performance. Consider the following optimizations:

  1. Preallocate and reuse temporary arrays and objects to reduce memory allocations.
  2. Use object pooling for frequently created objects like CharRenderInfo and TextChunk.
  3. Minimize the number of iterations and loops where possible.
  4. Use more efficient data structures, such as typed arrays, for performance-critical operations.

These optimizations can help improve the performance of the _updateLocalData method, especially when dealing with large amounts of text.

🧰 Tools
🪛 Biome

[error] 466-466: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 467-467: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 477-477: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 479-479: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


561-590: Use a profiler to analyze the performance of the _buildChunk method.

The _buildChunk method is responsible for building the vertex and index data for each text chunk. It's important to ensure that this method is performant, as it can be called frequently during text rendering.

Consider using a profiler to analyze the performance of this method and identify any potential bottlenecks. Pay attention to the following:

  1. Memory allocations and deallocations.
  2. Iteration and loop performance.
  3. Data structure access and manipulation.

Based on the profiling results, optimize the method as needed to improve its performance. This may involve techniques such as object pooling, preallocating arrays, or using more efficient data structures.

🧰 Tools
🪛 Biome

[error] 565-565: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 567-567: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


611-615: Consider using a more memory-efficient data structure for TextChunk.

The TextChunk class currently uses an array to store CharRenderInfo instances. Depending on the size of the text and the number of chunks, this can lead to significant memory usage.

Consider using a more memory-efficient data structure, such as a linked list or a custom allocator, to store the CharRenderInfo instances. This can help reduce memory overhead, especially when dealing with large amounts of text.

Additionally, explore the possibility of reusing TextChunk instances through object pooling to further optimize memory usage.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 4b2487d and ae63b66.

📒 Files selected for processing (17)
  • packages/core/src/2d/text/TextRenderer.ts (20 hunks)
  • packages/core/src/2d/text/TextUtils.ts (6 hunks)
  • packages/core/src/Entity.ts (10 hunks)
  • packages/core/src/input/pointer/emitter/PointerUIEventEmitter.ts (1 hunks)
  • packages/core/src/ui/Image.ts (1 hunks)
  • packages/core/src/ui/Text.ts (1 hunks)
  • packages/core/src/ui/UICanvas.ts (1 hunks)
  • packages/core/src/ui/UIGroup.ts (1 hunks)
  • packages/core/src/ui/UIRenderer.ts (1 hunks)
  • packages/core/src/ui/UIUtils.ts (1 hunks)
  • packages/core/src/ui/index.ts (1 hunks)
  • packages/core/src/ui/interactive/UIInteractive.ts (1 hunks)
  • packages/core/src/ui/interactive/transition/SpriteTransition.ts (1 hunks)
  • packages/core/src/ui/interactive/transition/Transition.ts (1 hunks)
  • packages/core/src/ui/interface/IUIElement.ts (1 hunks)
  • packages/core/src/ui/interface/IUIGraphics.ts (1 hunks)
  • packages/core/src/ui/interface/IUIGroupable.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/core/src/2d/text/TextUtils.ts
  • packages/core/src/ui/interactive/transition/SpriteTransition.ts
  • packages/core/src/ui/interface/IUIGraphics.ts
🧰 Additional context used
🪛 Biome
packages/core/src/Entity.ts

[error] 612-612: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/input/pointer/emitter/PointerUIEventEmitter.ts

[error] 85-85: The function should not return a value because its return type is void.

The function is here:

'void' signals the absence of value. The returned value is likely to be ignored by the caller.

(lint/correctness/noVoidTypeReturn)


[error] 102-102: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 102-102: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 222-222: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/ui/Text.ts

[error] 225-225: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 466-466: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 467-467: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 477-477: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 479-479: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 565-565: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 567-567: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/ui/UICanvas.ts

[error] 194-194: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 211-211: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 357-357: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 471-472: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 481-482: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 483-485: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

packages/core/src/ui/UIGroup.ts

[error] 105-105: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)


[error] 110-110: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)


[error] 153-153: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/ui/UIRenderer.ts

[error] 158-158: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 162-162: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 163-163: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 161-165: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

packages/core/src/ui/UIUtils.ts

[error] 16-153: Avoid classes that contain only static members.

Prefer using simple functions instead of classes with only static members.

(lint/complexity/noStaticOnlyClass)


[error] 53-53: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 72-72: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 130-130: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 130-130: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 131-131: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 131-131: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 132-132: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 132-132: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)

packages/core/src/ui/interactive/UIInteractive.ts

[error] 132-134: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

🔇 Additional comments (44)
packages/core/src/ui/interface/IUIGroupable.ts (1)

1-2: LGTM! Imports are well-organized.

The necessary dependencies are correctly imported using appropriate relative paths.

packages/core/src/ui/interface/IUIElement.ts (2)

1-3: LGTM! Imports are clean and well-organized.

All necessary types are properly imported, and there are no redundant imports.


10-10: Consider adding canvas modification handler.

The interface has _onEntityModify but lacks a corresponding method for handling canvas modifications. This asymmetry might indicate a missing feature.

Consider adding an _onCanvasModify method if canvas-level changes need to be tracked:

_onCanvasModify(flag: CanvasModifyFlags): void;
packages/core/src/ui/index.ts (2)

1-4: LGTM! Core exports are well-organized.

The core UI group and enum exports provide essential building blocks for the GUI infrastructure.


12-15: LGTM! Transition system exports are comprehensive.

The transition system appears well-thought-out with support for different transition types (Color, Scale, Sprite).

packages/core/src/ui/UIUtils.ts (1)

137-137: Add error handling for RHI operations

The RHI operation could fail in certain scenarios. Consider adding error handling.

packages/core/src/ui/UIGroup.ts (1)

164-177: Verify entity listener cleanup

The entity listener registration in the while loop could potentially lead to memory leaks if the entity hierarchy changes frequently. Consider adding a verification step to ensure all listeners are properly cleaned up.

✅ Verification successful

Entity listener cleanup is properly implemented

The codebase shows a robust implementation of entity listener cleanup:

  • In UIGroup, listeners are properly unregistered in a loop before clearing the array: entityListeners[i]._unRegisterModifyListener(this._onEntityModify)
  • When replacing listeners in the hierarchy, old listeners are unregistered before new ones are registered: preListener?._unRegisterModifyListener(this._onEntityModify)
  • The pattern is consistently used across related components (UIUtils, UICanvas) showing a systematic approach to listener management

The original concern about potential memory leaks is unfounded as the code maintains proper cleanup of entity listeners.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for potential memory leaks in entity listener management
echo "Searching for entity listener registration patterns..."
ast-grep --pattern 'entity._registerModifyListener($_)'

echo "Searching for corresponding cleanup patterns..."
ast-grep --pattern 'entity._unRegisterModifyListener($_)'

# Look for potential memory leak patterns in test files
echo "Searching for test cases covering listener cleanup..."
rg -l "test.*UIGroup.*cleanup" -t ts

Length of output: 695


Script:

#!/bin/bash
# Let's search for unregister patterns with a broader context
echo "Searching for unregister patterns..."
rg "_unRegisterModifyListener" -A 2 -B 2

# Check for cleanup in UIGroup class destructor or dispose methods
echo "Searching for cleanup methods in UIGroup..."
ast-grep --pattern 'class UIGroup {
  $$$
  dispose() {
    $$$
  }
  $$$
}'

# Check if there are any tests for UIGroup
echo "Searching for UIGroup test files..."
fd "UIGroup.*test" -e ts

Length of output: 2984

packages/core/src/ui/UIRenderer.ts (1)

19-20: ⚠️ Potential issue

Implement ISpriteRenderer interface.

The class should implement the ISpriteRenderer interface to maintain consistency with the sprite rendering system.

-export abstract class UIRenderer extends Renderer implements IUIGraphics {
+export abstract class UIRenderer extends Renderer implements IUIGraphics, ISpriteRenderer {

Likely invalid or redundant comment.

packages/core/src/ui/UICanvas.ts (1)

470-492: 🛠️ Refactor suggestion

Document intentional switch case fallthrough.

The switch cases in _setRealRenderMode are falling through intentionally to execute multiple actions, but this pattern should be explicitly documented to prevent confusion.

Apply this diff to add fallthrough comments:

      switch (curRealMode) {
        case CanvasRenderMode.ScreenSpaceOverlay:
          this._addCanvasListener();
+         // fallthrough intentional
        case CanvasRenderMode.ScreenSpaceCamera:
          this._adapterPoseInScreenSpace();
          this._adapterSizeInScreenSpace();
+         // fallthrough intentional
        case CanvasRenderMode.WorldSpace:
          componentsManager.addUICanvas(curRealMode, this);
          break;

Likely invalid or redundant comment.

🧰 Tools
🪛 Biome

[error] 471-472: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 481-482: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 483-485: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

packages/core/src/2d/text/TextRenderer.ts (2)

35-35: Fix array initialization.

The array initialization syntax is incorrect. Using Array<TextChunk>() without the new keyword won't create a new array instance.


475-478: 🛠️ Refactor suggestion

Add error handling for text measurement edge cases.

The text measurement logic should handle edge cases such as extremely large text or invalid font states.

  private _updateLocalData(): void {
    const { min, max } = this._localBounds;
    const charRenderInfos = TextRenderer._charRenderInfos;
    const charFont = this._getSubFont();
+   try {
      const textMetrics = this.enableWrapping
        ? TextUtils.measureTextWithWrap(this, this.width, this.height)
        : TextUtils.measureTextWithoutWrap(this, this.height);
+   } catch (error) {
+     console.error('Text measurement failed:', error);
+     min.set(0, 0, 0);
+     max.set(0, 0, 0);
+     return;
+   }
packages/core/src/Entity.ts (8)

99-104: LGTM! Property changes enhance state management.

The addition of _updateFlagManager and conversion of transform to a private property with getter improves encapsulation.


107-110: LGTM! Transform getter maintains backward compatibility.

The getter provides controlled access to the private _transform property while maintaining the public API.


527-530: LGTM! Proper cleanup of update flag manager.

The cleanup ensures no memory leaks by removing all listeners and nullifying the reference.


603-606: LGTM! Parent change handling is comprehensive.

The method properly updates transform and dispatches modification flags.


695-695: LGTM! Parent change notification is properly placed.

The _setParentChange call is correctly placed after all parent-related updates.


771-771: LGTM! Sibling index modification notification.

The dispatch of the sibling index modification flag is properly placed after the index update.


783-783: LGTM! World matrix update uses private transform.

The update correctly uses the private _transform property.


790-797: LGTM! Comprehensive entity modification flags.

The enum provides a clear set of flags for tracking different types of entity modifications.

packages/core/src/ui/interactive/transition/Transition.ts (4)

26-32: ****
Use deep comparison in setters to detect changes in complex objects.

Also applies to: 35-41, 47-53, 59-65


85-95: ****
Prevent division by zero when adjusting _countDown in duration setter.


128-132: ****
Clamp weight between 0 and 1 in _updateValue to ensure valid interpolation.


1-155: LGTM!

The Transition class provides a solid foundation for managing UI transitions. The use of generics allows flexibility in handling different value types and UI renderers. The class encapsulates the transition logic effectively, with clear separation of concerns between the abstract methods and the internal implementation.

The class follows good practices such as:

  • Using getters and setters for properties to encapsulate internal state changes.
  • Providing a clear API for setting the transition state and updating values.
  • Handling edge cases like negative duration values and adjusting the countdown accordingly.
  • Utilizing abstract methods to allow customization of transition behavior by subclasses.

The code is well-structured, readable, and maintainable. The comments provide helpful insights into the purpose and usage of the class and its methods.

Overall, the Transition class is a valuable addition to the UI framework and lays a solid groundwork for implementing various types of transitions in a consistent and extensible manner.

packages/core/src/ui/interactive/UIInteractive.ts (7)

1-11: LGTM!

The imports are well-organized and follow a logical order. The imported entities are relevant to the functionality of the UIInteractive class.


12-82: LGTM!

The UIInteractive class is well-structured and implements the necessary properties and methods for managing interactive UI elements. The use of TypeScript features such as access modifiers, getters/setters, and type generics enhances the code's readability and maintainability.

The class provides a clean API for managing transitions and updating the interactive state based on pointer events. The onUpdate method ensures that transitions are updated efficiently.


84-102: LGTM!

The pointer event handling methods (onPointerBeginDrag, onPointerEndDrag, onPointerEnter, onPointerExit) are implemented correctly. They update the internal state variables and trigger state updates appropriately.


107-123: LGTM!

The _onEnableInScene and _onDisableInScene methods handle the registration and unregistration of the element with the UI utilities effectively. They ensure proper integration with the scene and update the interactive state accordingly.


147-155: LGTM!

The _onGroupModify method correctly handles the GroupModifyFlags.Interactive flag. It updates the runtime interactive state and triggers a state update when necessary.


157-174: LGTM!

The private methods _updateState and _getInteractiveState are implemented correctly. They manage the current state of interactivity based on the runtime interactive flag and pointer interactions.


131-134: ⚠️ Potential issue


The previous review comment regarding the missing break statement is still valid. Please address this issue to prevent unintended fallthrough.

Apply this diff to add the missing break statement:

 131       case EntityModifyFlags.UICanvasEnableInScene:
 132       case EntityModifyFlags.Parent:
 133         UIUtils.registerElementToCanvas(this, UIUtils.getRootCanvasInParent(this._entity));
 134         UIUtils.registerEntityListener(this);
+135         break;
 136       case EntityModifyFlags.UIGroupEnableInScene:
🧰 Tools
🪛 Biome

[error] 132-134: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

packages/core/src/input/pointer/emitter/PointerUIEventEmitter.ts (6)

85-85: ****

🧰 Tools
🪛 Biome

[error] 85-85: The function should not return a value because its return type is void.

The function is here:

'void' signals the absence of value. The returned value is likely to be ignored by the caller.

(lint/correctness/noVoidTypeReturn)


88-89: ****


102-102: ****

🧰 Tools
🪛 Biome

[error] 102-102: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 102-102: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


167-190: ****


222-222: ****

🧰 Tools
🪛 Biome

[error] 222-222: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


1-236: The PointerUIEventEmitter class provides a structured approach to managing pointer interactions with UI components.

The class extends the PointerEventEmitter and overrides key methods to handle pointer events specifically for UI components. It maintains references to the currently entered, pressed, and dragged UI elements, enabling accurate event processing based on pointer interactions.

The implementation follows a clear and logical flow, with well-defined responsibilities for each method. The code is modular, making it easier to understand and maintain. The use of private helper methods for common tasks like updating the raycast, bubbling events, and composing paths further enhances the code organization.

Overall, the PointerUIEventEmitter class is a solid addition to the pointer event handling system, providing a dedicated mechanism for managing UI component interactions.

🧰 Tools
🪛 Biome

[error] 85-85: The function should not return a value because its return type is void.

The function is here:

'void' signals the absence of value. The returned value is likely to be ignored by the caller.

(lint/correctness/noVoidTypeReturn)


[error] 102-102: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 102-102: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 222-222: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/ui/Image.ts (7)

42-43: Ensure alphaHitTestMinimumThreshold is clamped between 0 and 1

The setter for alphaHitTestMinimumThreshold correctly clamps the value. Just make sure that any value assignments are within the expected range.


52-71: Update assembler initialization in drawMode setter

The logic for updating the _assembler based on drawMode is appropriate. This ensures the correct assembler is used for rendering.


81-87: Correct handling of tileMode setter

The tileMode setter correctly updates the dirty flags when the mode changes. This maintains the integrity of the rendering process.


113-130: Proper management of sprite resource in sprite setter

The setter effectively manages the reference counting and listener management for sprite resources. This helps prevent memory leaks and ensures resources are properly cleaned up.


285-298: Resource cleanup in _onDestroy

The _onDestroy method properly handles resource cleanup by decreasing reference counts and removing listeners. This prevents potential memory leaks.


300-341: Comprehensive handling in _onSpriteChange method

The _onSpriteChange method effectively updates the dirty flags based on the type of sprite modification. This ensures the renderer reacts appropriately to sprite changes.


343-356: Verify the UV calculation in _getUVByLocalPosition

While the method delegates UV calculation to the assembler, ensure that for SpriteDrawMode.Tiled, the correct assembler is used. Currently, it calls SlicedSpriteAssembler.getUVByLocalPosition for both Sliced and Tiled modes. Is this intentional?

Please confirm that using SlicedSpriteAssembler for Tiled mode is correct.

If not, consider the following change:

case SpriteDrawMode.Tiled:
-  return SlicedSpriteAssembler.getUVByLocalPosition(this, size.x, size.y, pivot, position, out);
+  return TiledSpriteAssembler.getUVByLocalPosition(this, size.x, size.y, pivot, position, out);
packages/core/src/ui/Text.ts (1)

1-627: Comprehensive and well-structured implementation of the Text class.

The Text class provides a robust and feature-rich implementation for rendering text in a 2D UI context. It effectively manages text properties, rendering states, and efficiently handles text layout and rendering.

Key highlights:

  • Utilizes dirty flags to optimize rendering and minimize unnecessary updates.
  • Implements proper resource management, including reference counting for fonts and memory pooling for character render information.
  • Supports various text formatting options such as alignment, wrapping, overflow modes, and font styles.
  • Efficiently manages text chunks and batches rendering for optimal performance.
  • Integrates with the ISpriteRenderer interface and other UI components.

The code follows good practices such as:

  • Appropriate use of access modifiers for encapsulation.
  • Meaningful naming conventions for variables, methods, and enums.
  • Proper error handling and input validation.
  • Efficient memory management through object pooling.
  • Modular and reusable design with clear separation of concerns.

Overall, the Text class is a solid foundation for text rendering in the UI system.

🧰 Tools
🪛 Biome

[error] 225-225: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 466-466: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 467-467: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 477-477: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 479-479: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 565-565: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 567-567: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

Comment on lines 4 to 6
export interface IUIGroupable extends IUIElement {
_group: UIGroup;
_indexInGroup: number;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add JSDoc documentation and improve type safety.

The interface and its properties should be documented for better maintainability. Also, consider the following improvements:

  1. Add JSDoc comments explaining the purpose of the interface and its properties
  2. Consider using protected or private modifiers since properties are prefixed with underscore
  3. Add type constraints for _indexInGroup to ensure non-negative values

Here's the suggested improvement:

+/**
+ * Interface for UI elements that can be grouped together.
+ * Provides necessary properties and methods for group management.
+ */
 export interface IUIGroupable extends IUIElement {
+  /** The group this element belongs to */
   _group: UIGroup;
+  /** Zero-based index of this element within its group */
-  _indexInGroup: number;
+  _indexInGroup: NonNegativeInteger;
 }

+ type NonNegativeInteger = number & { __brand: 'NonNegativeInteger' };

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 200 to 222
_raycast(ray: Ray, out: HitResult, distance: number = Number.MAX_SAFE_INTEGER): boolean {
const plane = UIRenderer._tempPlane;
const transform = this._transform;
const normal = plane.normal.copyFrom(transform.worldForward);
plane.distance = -Vector3.dot(normal, transform.worldPosition);
const curDistance = ray.intersectPlane(plane);
if (curDistance >= 0 && curDistance < distance) {
const hitPointWorld = ray.getPoint(curDistance, UIRenderer._tempVec30);
const worldMatrixInv = UIRenderer._tempMat;
Matrix.invert(transform.worldMatrix, worldMatrixInv);
const localPosition = UIRenderer._tempVec31;
Vector3.transformCoordinate(hitPointWorld, worldMatrixInv, localPosition);
if (this._hitTest(localPosition)) {
out.distance = curDistance;
out.entity = this._entity;
out.component = this;
out.normal.copyFrom(normal);
out.point.copyFrom(hitPointWorld);
return true;
}
}
return false;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Optimize raycast performance and readability.

The raycast implementation can be optimized for better performance and readability:

  1. Cache the plane intersection result
  2. Use early returns for better flow control
  3. Destructure transform properties
 _raycast(ray: Ray, out: HitResult, distance: number = Number.MAX_SAFE_INTEGER): boolean {
   const plane = UIRenderer._tempPlane;
-  const transform = this._transform;
+  const { worldForward, worldMatrix, worldPosition } = this._transform;
-  const normal = plane.normal.copyFrom(transform.worldForward);
+  const normal = plane.normal.copyFrom(worldForward);
-  plane.distance = -Vector3.dot(normal, transform.worldPosition);
+  plane.distance = -Vector3.dot(normal, worldPosition);
   
   const intersectionDistance = ray.intersectPlane(plane);
-  if (curDistance >= 0 && curDistance < distance) {
-    const hitPointWorld = ray.getPoint(curDistance, UIRenderer._tempVec30);
-    const worldMatrixInv = UIRenderer._tempMat;
-    Matrix.invert(transform.worldMatrix, worldMatrixInv);
+  if (intersectionDistance < 0 || intersectionDistance >= distance) {
+    return false;
+  }

+  const hitPointWorld = ray.getPoint(intersectionDistance, UIRenderer._tempVec30);
+  const worldMatrixInv = UIRenderer._tempMat;
+  Matrix.invert(worldMatrix, worldMatrixInv);

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 156 to 171
switch (flag) {
case EntityModifyFlags.SiblingIndex:
this._rootCanvas && (this._rootCanvas._hierarchyDirty = true);
break;
case EntityModifyFlags.UICanvasEnableInScene:
case EntityModifyFlags.Parent:
const rootCanvas = UIUtils.getRootCanvasInParent(this._entity);
rootCanvas && (rootCanvas._hierarchyDirty = true);
UIUtils.registerElementToCanvas(this, rootCanvas);
UIUtils.registerEntityListener(this);
case EntityModifyFlags.UIGroupEnableInScene:
UIUtils.registerElementToGroup(this, UIUtils.getGroupInParents(this._entity));
break;
default:
break;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix switch statement safety issues.

The switch statement has several issues that need to be addressed:

  1. Case fallthrough between Parent and UIGroupEnableInScene
  2. Variable declaration scope
  3. Assignments within expressions
 switch (flag) {
   case EntityModifyFlags.SiblingIndex: {
-    this._rootCanvas && (this._rootCanvas._hierarchyDirty = true);
+    if (this._rootCanvas) {
+      this._rootCanvas._hierarchyDirty = true;
+    }
     break;
   }
   case EntityModifyFlags.UICanvasEnableInScene:
-  case EntityModifyFlags.Parent:
+  case EntityModifyFlags.Parent: {
     const rootCanvas = UIUtils.getRootCanvasInParent(this._entity);
-    rootCanvas && (rootCanvas._hierarchyDirty = true);
+    if (rootCanvas) {
+      rootCanvas._hierarchyDirty = true;
+    }
     UIUtils.registerElementToCanvas(this, rootCanvas);
     UIUtils.registerEntityListener(this);
+    break;
+  }
   case EntityModifyFlags.UIGroupEnableInScene:
     UIUtils.registerElementToGroup(this, UIUtils.getGroupInParents(this._entity));
     break;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
switch (flag) {
case EntityModifyFlags.SiblingIndex:
this._rootCanvas && (this._rootCanvas._hierarchyDirty = true);
break;
case EntityModifyFlags.UICanvasEnableInScene:
case EntityModifyFlags.Parent:
const rootCanvas = UIUtils.getRootCanvasInParent(this._entity);
rootCanvas && (rootCanvas._hierarchyDirty = true);
UIUtils.registerElementToCanvas(this, rootCanvas);
UIUtils.registerEntityListener(this);
case EntityModifyFlags.UIGroupEnableInScene:
UIUtils.registerElementToGroup(this, UIUtils.getGroupInParents(this._entity));
break;
default:
break;
}
switch (flag) {
case EntityModifyFlags.SiblingIndex: {
if (this._rootCanvas) {
this._rootCanvas._hierarchyDirty = true;
}
break;
}
case EntityModifyFlags.UICanvasEnableInScene:
case EntityModifyFlags.Parent: {
const rootCanvas = UIUtils.getRootCanvasInParent(this._entity);
if (rootCanvas) {
rootCanvas._hierarchyDirty = true;
}
UIUtils.registerElementToCanvas(this, rootCanvas);
UIUtils.registerEntityListener(this);
break;
}
case EntityModifyFlags.UIGroupEnableInScene:
UIUtils.registerElementToGroup(this, UIUtils.getGroupInParents(this._entity));
break;
default:
break;
}
🧰 Tools
🪛 Biome

[error] 158-158: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 162-162: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 163-163: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 161-165: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

Comment on lines 101 to 102
//@todo: Always update world positions to buffer, should opt
// super._updateTransformShaderData(context, onlyMVP, true);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Commented super call should be uncommented to match other renderer implementations

The codebase analysis shows that:

  • The base Renderer class implements _updateTransformShaderData
  • Other renderer classes (SpriteRenderer, TextRenderer, ParticleRenderer, etc.) consistently call super._updateTransformShaderData with the same TODO comment
  • UIRenderer is the only implementation that has commented out the super call while still having the same TODO

The commented super call should be uncommented to maintain consistency with other renderer implementations and ensure proper transform updates. The optimization mentioned in the TODO can be addressed separately across all renderers.

🔗 Analysis chain

Address TODO comment and clarify super call.

There's a TODO comment about optimizing world position updates and a commented-out super call that needs to be addressed:

  1. Implement the optimization for world position updates
  2. Clarify whether the super call is necessary
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if the super class implementation is different
ast-grep --pattern $'class Renderer {
  $$$
  _updateTransformShaderData(context: $_) {
    $$$
  }
  $$$
}'

Length of output: 109


Script:

#!/bin/bash
# Let's try a broader search for the base class and its method
ast-grep --pattern '_updateTransformShaderData'

# Also search for any related files
fd -e ts -e js "Renderer"

# And check the specific file content
rg -A 5 -B 5 "_updateTransformShaderData" 

Length of output: 9204

Comment on lines 167 to 168
// @ts-ignore
this._referenceResolution._onValueChanged = this._onReferenceResolutionChanged;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove @ts-ignore by extending Vector2 type.

Using @ts-ignore suppresses type checking and can hide potential issues. Create a proper type definition instead.

Create an interface extending Vector2:

interface Vector2WithCallback extends Vector2 {
  _onValueChanged?: () => void;
}

Then update the property type:

-  private _referenceResolution: Vector2 = new Vector2(800, 600);
+  private _referenceResolution: Vector2WithCallback = new Vector2(800, 600);

set referenceResolution(val: Vector2) {
const { _referenceResolution: referenceResolution } = this;
if (referenceResolution === val) return;
(referenceResolution.x !== val.x || referenceResolution.y !== val.y) && referenceResolution.copyFrom(val);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve readability by avoiding assignments in expressions.

Using assignments within expressions makes the code harder to read and understand.

Apply this diff to improve readability:

-    (referenceResolution.x !== val.x || referenceResolution.y !== val.y) && referenceResolution.copyFrom(val);
+    if (referenceResolution.x !== val.x || referenceResolution.y !== val.y) {
+      referenceResolution.copyFrom(val);
+    }

-    child.isActive && (depth = this._walk(child, elements, depth));
+    if (child.isActive) {
+      depth = this._walk(child, elements, depth);
+    }

Also applies to: 357-357

this.setMaterial(engine._basicResources.textDefaultMaterial);
//@ts-ignore
this._color._onValueChanged = this._onColorChanged.bind(this);
this._color._onValueChanged = this._onColorChange.bind(this);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid type assertion in color change callback binding.

The type assertion (@ts-ignore) suggests a potential type safety issue. Consider properly typing the color object or implementing a proper event system.

-    //@ts-ignore
-    this._color._onValueChanged = this._onColorChange.bind(this);
+    // Option 1: Define proper event interface
+    interface ColorWithCallback extends Color {
+      _onValueChanged?: () => void;
+    }
+    (this._color as ColorWithCallback)._onValueChanged = this._onColorChange.bind(this);
+
+    // Option 2: Use event emitter pattern
+    this._color.on('change', this._onColorChange.bind(this));

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 611 to 626
_registerModifyListener(onChange: (flag: EntityModifyFlags) => void): void {
(this._updateFlagManager ||= new UpdateFlagManager()).addListener(onChange);
}

/**
* @internal
*/
_unRegisterModifyListener(onChange: (flag: EntityModifyFlags) => void): void {
this._updateFlagManager?.removeListener(onChange);
}

/**
* @internal
*/
_dispatchModify(flag: EntityModifyFlags): void {
this._updateFlagManager?.dispatch(flag);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Fix assignment in expression for better readability.

The modify listener registration contains an assignment within an expression which could be confusing.

-    (this._updateFlagManager ||= new UpdateFlagManager()).addListener(onChange);
+    if (!this._updateFlagManager) {
+      this._updateFlagManager = new UpdateFlagManager();
+    }
+    this._updateFlagManager.addListener(onChange);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
_registerModifyListener(onChange: (flag: EntityModifyFlags) => void): void {
(this._updateFlagManager ||= new UpdateFlagManager()).addListener(onChange);
}
/**
* @internal
*/
_unRegisterModifyListener(onChange: (flag: EntityModifyFlags) => void): void {
this._updateFlagManager?.removeListener(onChange);
}
/**
* @internal
*/
_dispatchModify(flag: EntityModifyFlags): void {
this._updateFlagManager?.dispatch(flag);
_registerModifyListener(onChange: (flag: EntityModifyFlags) => void): void {
if (!this._updateFlagManager) {
this._updateFlagManager = new UpdateFlagManager();
}
this._updateFlagManager.addListener(onChange);
}
/**
* @internal
*/
_unRegisterModifyListener(onChange: (flag: EntityModifyFlags) => void): void {
this._updateFlagManager?.removeListener(onChange);
}
/**
* @internal
*/
_dispatchModify(flag: EntityModifyFlags): void {
this._updateFlagManager?.dispatch(flag);
🧰 Tools
🪛 Biome

[error] 612-612: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

Comment on lines 22 to 86
override _processRaycast(scenes: readonly Scene[], pointer: Pointer): void {
const { _tempRay: ray } = PointerEventEmitter;
const hitResult = this._hitResult;
const { position } = pointer;
const { x, y } = position;
for (let i = scenes.length - 1; i >= 0; i--) {
const scene = scenes[i];
if (!scene.isActive || scene.destroyed) continue;
const { _componentsManager: componentsManager } = scene;

/** Overlay Canvas */
let canvasElements = componentsManager._overlayCanvases;
ray.origin.set(position.x, position.y, 1);
ray.direction.set(0, 0, -1);
for (let j = canvasElements.length - 1; j >= 0; j--) {
if (canvasElements.get(j).raycast(ray, hitResult)) {
this._updateRaycast(<Component>hitResult.component, pointer);
return;
}
}

const cameras = componentsManager._activeCameras;
for (let j = cameras.length - 1; j >= 0; j--) {
const camera = cameras.get(j);
if (camera.renderTarget) continue;
const { pixelViewport } = camera;
if (
x < pixelViewport.x ||
y < pixelViewport.y ||
x > pixelViewport.x + pixelViewport.width ||
y > pixelViewport.y + pixelViewport.height
) {
continue;
}
camera.screenPointToRay(pointer.position, ray);

/** Other canvases */
const cameraPosition = camera.entity.transform.position;
/** Sort by rendering order */
canvasElements = componentsManager._canvases;
for (let k = 0, n = canvasElements.length; k < n; k++) {
canvasElements.get(k)._updateSortDistance(cameraPosition);
}
canvasElements.sort((a, b) => a.sortOrder - b.sortOrder || a._sortDistance - b._sortDistance);
for (let k = 0, n = canvasElements.length; k < n; k++) {
canvasElements.get(k)._canvasIndex = k;
}
const farClipPlane = camera.farClipPlane;
/** Post-rendering first detection */
for (let k = 0, n = canvasElements.length; k < n; k++) {
const canvas = canvasElements.get(k);
if (canvas.renderCamera !== camera) continue;
if (canvas.raycast(ray, hitResult, farClipPlane)) {
this._updateRaycast(<Component>hitResult.component, pointer);
return;
}
}
if (camera.clearFlags & CameraClearFlags.Color) {
this._updateRaycast(null);
return;
}
}
}
return null;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider refactoring the _processRaycast method to improve readability and maintainability.

The _processRaycast method is quite complex with multiple nested loops and conditions. Breaking it down into smaller, focused methods would enhance the code structure and make it easier to understand and maintain.

Suggestions:

  1. Extract the logic for processing overlay canvases into a separate method.
  2. Move the code for handling camera-specific processing to a dedicated method.
  3. Consider creating helper methods for common tasks like sorting canvases and updating the raycast.

By decomposing the method into smaller, single-responsibility functions, the overall readability and maintainability of the code will improve.

🧰 Tools
🪛 Biome

[error] 85-85: The function should not return a value because its return type is void.

The function is here:

'void' signals the absence of value. The returned value is likely to be ignored by the caller.

(lint/correctness/noVoidTypeReturn)

Comment on lines 145 to 178
protected override _hitTest(localPosition: Vector3): boolean {
let { x, y } = localPosition;
const uiTransform = <UITransform>this._transform;
const { x: width, y: height } = uiTransform.size;
const { x: pivotX, y: pivotY } = uiTransform.pivot;
const { x: paddingLeft, y: paddingBottom, z: paddingRight, w: paddingTop } = this.raycastPadding;
if (
x < -width * pivotX + paddingLeft ||
x > width * (1 - pivotX) - paddingRight ||
y < -height * pivotY + paddingTop ||
y > height * (1 - pivotY) - paddingBottom
) {
return false;
}
const alphaHitTestMinimumThreshold = this._alphaHitTestMinimumThreshold;
if (alphaHitTestMinimumThreshold <= 0) {
return true;
}
const texture = this.sprite?.texture;
if (!texture) {
return false;
}
const uv = Image._tempVec2;
if (!this._getUVByLocalPosition(localPosition, uv)) {
return false;
}
const pixel = Image._tempUnit8Array;
texture.getPixelBuffer(Math.floor(uv.x * texture.width), Math.floor(uv.y * texture.height), 1, 1, 0, pixel);
if (pixel[3] >= alphaHitTestMinimumThreshold * 255) {
return true;
} else {
return false;
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Potential performance impact in _hitTest method

Using texture.getPixelBuffer inside the _hitTest method may introduce performance overhead, especially if the method is called frequently. Consider caching the pixel data or implementing an alternative approach to minimize performance impact.

To optimize, you might consider:

+// Cache the pixel data if possible to reduce repeated texture reads

Committable suggestion skipped: line range outside the PR's diff.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 19

🧹 Outside diff range and nitpick comments (24)
packages/core/src/ui/interface/IUIGroupable.ts (1)

8-8: Consider implementing a formal event system instead of internal callbacks.

The _onGroupModify method appears to be an internal callback for group modifications. This approach might be improved by:

  1. Using a formal event system
  2. Providing a clearer public API
  3. Better encapsulating implementation details

Consider this alternative approach:

-  _onGroupModify(flag: GroupModifyFlags): void;
+  onGroupChanged(event: GroupChangeEvent): void;

Where GroupChangeEvent could be a well-defined event object:

interface GroupChangeEvent {
  type: GroupChangeType;
  source: UIGroup;
  // other relevant event data
}

This would:

  • Provide a clearer contract for group-related changes
  • Follow standard event handling patterns
  • Be more maintainable and extensible
packages/core/src/ui/interface/IUIElement.ts (2)

4-11: Add JSDoc documentation to improve code maintainability.

The interface and its members lack documentation. Consider adding JSDoc comments to describe:

  • The purpose and responsibility of the IUIElement interface
  • The role of each property and method
  • Parameter and return type descriptions for _onEntityModify

Example documentation:

/**
 * Represents a UI element within the rendering engine.
 * Defines the contract for managing UI element lifecycle and hierarchy.
 */
export interface IUIElement {
  /** The entity associated with this UI element */
  _entity: Entity;
  /** Array of parent entities in the hierarchy */
  _parents: Entity[];
  /** Reference to the root canvas containing this element */
  _rootCanvas: UICanvas;
  /** Index position of this element within the canvas */
  _indexInCanvas: number;

  /**
   * Handles modifications to the associated entity
   * @param flag - Flags indicating the type of entity modification
   */
  _onEntityModify(flag: EntityModifyFlags): void;
}

4-11: Consider adding essential lifecycle methods.

The interface could benefit from additional lifecycle methods to handle common UI element operations:

Consider adding these methods:

export interface IUIElement {
  // ... existing members ...

  /** Called when the element is mounted to the DOM */
  _onMount(): void;

  /** Called when the element is unmounted from the DOM */
  _onUnmount(): void;

  /** 
   * Called when the element's visibility changes
   * @param visible - New visibility state
   */
  _onVisibilityChange(visible: boolean): void;

  /**
   * Called when the element needs to update its layout
   * @param force - Whether to force a full layout update
   */
  _onLayout(force?: boolean): void;
}
packages/core/src/ui/index.ts (1)

5-11: Consider grouping related UI components.

The exports are logically organized, but could benefit from clearer visual separation between infrastructure (UICanvas, UIRenderer, UITransform) and UI elements (Button, Image, Text).

Consider adding comments to separate these groups:

 export { UICanvas } from "./UICanvas";
 export { UIRenderer } from "./UIRenderer";
 export { UITransform } from "./UITransform";
+
+// UI Elements
 export { Button } from "./Button";
 export { Image } from "./Image";
 export { Text } from "./Text";
packages/core/src/ui/interactive/UIInteractive.ts (2)

41-50: Consider extracting runtime interactive state logic.

The setter contains complex state management logic that could be moved to a separate method for better maintainability. Also, the optional chaining could lead to unexpected behavior if _group is undefined.

Consider this refactor:

 set interactive(value: boolean) {
   if (this._interactive !== value) {
     this._interactive = value;
-    const runtimeInteractive = value && this._group?._getGlobalInteractive();
-    if (this._runtimeInteractive !== runtimeInteractive) {
-      this._runtimeInteractive = runtimeInteractive;
-      this._updateState(true);
-    }
+    this._updateRuntimeInteractive(value);
   }
 }
+
+private _updateRuntimeInteractive(value: boolean): void {
+  const globalInteractive = this._group?._getGlobalInteractive() ?? true;
+  const runtimeInteractive = value && globalInteractive;
+  if (this._runtimeInteractive !== runtimeInteractive) {
+    this._runtimeInteractive = runtimeInteractive;
+    this._updateState(true);
+  }
+}

147-155: Improve group modification logic.

The group modification logic has similar issues to the interactive setter with optional chaining and complex state management.

Consider this refactor:

 _onGroupModify(flag: GroupModifyFlags): void {
   if (flag & GroupModifyFlags.Interactive) {
-    const runtimeInteractive = this._interactive && (this._group?._getGlobalInteractive() || true);
-    if (this._runtimeInteractive !== runtimeInteractive) {
-      this._runtimeInteractive = runtimeInteractive;
-      this._updateState(true);
-    }
+    this._updateRuntimeInteractive(this._interactive);
   }
 }
packages/core/src/ui/UIUtils.ts (2)

1-16: Consider using a singleton pattern instead of static members.

While static analysis suggests converting to standalone functions, the shared state (_renderQueue, _virtualCamera, _viewport) suggests this class would benefit from a singleton pattern. This would provide better encapsulation and testability while maintaining the same usage pattern.

Example implementation:

export class UIUtils {
  private static instance: UIUtils;
  
  private _renderQueue: RenderQueue;
  private _virtualCamera: VirtualCamera;
  private _viewport: Vector4;

  private constructor() {
    // Initialize members
  }

  static getInstance(): UIUtils {
    if (!UIUtils.instance) {
      UIUtils.instance = new UIUtils();
    }
    return UIUtils.instance;
  }

  // Convert static methods to instance methods
}

128-152: Improve readability of matrix operations.

The direct matrix element assignments are hard to read and maintain. Consider using matrix utility methods or adding explanatory comments:

-    (projectE[0] = 2 / canvas.width), (projectE[5] = 2 / canvas.height), (projectE[10] = 0);
+    // Set orthographic projection matrix elements
+    projectE[0] = 2 / canvas.width;  // Scale X
+    projectE[5] = 2 / canvas.height; // Scale Y
+    projectE[10] = 0;               // Flatten Z

-    (viewE[12] = -transform.position.x), (viewE[13] = -transform.position.y);
+    // Set view translation
+    viewE[12] = -transform.position.x; // Translate X
+    viewE[13] = -transform.position.y; // Translate Y

Consider creating helper methods for these matrix operations:

private static setOrthographicProjection(matrix: Matrix, width: number, height: number): void {
  const elements = matrix.elements;
  elements[0] = 2 / width;
  elements[5] = 2 / height;
  elements[10] = 0;
}

private static setViewTranslation(matrix: Matrix, position: Vector3): void {
  const elements = matrix.elements;
  elements[12] = -position.x;
  elements[13] = -position.y;
}
🧰 Tools
🪛 Biome

[error] 130-130: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 130-130: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 131-131: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 131-131: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 132-132: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 132-132: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)

packages/core/src/ui/UIRenderer.ts (2)

22-28: Consider implementing object pooling for temporary objects.

The static temporary objects could be managed through an object pool to reduce garbage collection pressure during frequent UI updates.

Consider implementing an object pool:

class UIRendererObjectPool {
  private static _vec3Pool: Vector3[] = [];
  private static _matrixPool: Matrix[] = [];
  
  static acquireVector3(): Vector3 {
    return this._vec3Pool.pop() || new Vector3();
  }
  
  static releaseVector3(vec: Vector3): void {
    vec.set(0, 0, 0);
    this._vec3Pool.push(vec);
  }
}

200-222: Optimize raycast implementation.

The raycast method could be optimized by:

  1. Caching the plane intersection result
  2. Using early returns for better readability
  3. Using more descriptive variable names

Consider this optimization:

 _raycast(ray: Ray, out: HitResult, distance: number = Number.MAX_SAFE_INTEGER): boolean {
   const plane = UIRenderer._tempPlane;
   const { transform } = this._entity;
   const normal = plane.normal.copyFrom(transform.worldForward);
   plane.distance = -Vector3.dot(normal, transform.worldPosition);
   
-  const curDistance = ray.intersectPlane(plane);
+  const intersectionDistance = ray.intersectPlane(plane);
-  if (curDistance >= 0 && curDistance < distance) {
+  if (intersectionDistance < 0 || intersectionDistance >= distance) {
+    return false;
+  }

   const hitPointWorld = ray.getPoint(intersectionDistance, UIRenderer._tempVec30);
   const worldMatrixInv = UIRenderer._tempMat;
   Matrix.invert(transform.worldMatrix, worldMatrixInv);
   
   const localPosition = UIRenderer._tempVec31;
   Vector3.transformCoordinate(hitPointWorld, worldMatrixInv, localPosition);
   
-  if (this._hitTest(localPosition)) {
-    out.distance = curDistance;
-    out.entity = this._entity;
-    out.component = this;
-    out.normal.copyFrom(normal);
-    out.point.copyFrom(hitPointWorld);
-    return true;
-  }
-  return false;
+  if (!this._hitTest(localPosition)) {
+    return false;
+  }
+
+  out.distance = intersectionDistance;
+  out.entity = this._entity;
+  out.component = this;
+  out.normal.copyFrom(normal);
+  out.point.copyFrom(hitPointWorld);
+  return true;
packages/core/src/2d/text/TextUtils.ts (2)

99-104: Add JSDoc documentation for the updated method signature.

Please add documentation for the new parameters to improve maintainability and developer experience.

+/**
+ * Measures text with word wrapping enabled.
+ * @param renderer - The text renderer or Text component
+ * @param rendererWidth - The maximum width for text wrapping
+ * @param rendererHeight - The height of the renderer
+ * @param lineSpacing - The spacing between lines
+ * @returns TextMetrics containing the measured text dimensions and line information
+ */
 static measureTextWithWrap(

276-280: Add JSDoc documentation for the updated method signature.

Please add documentation for the new parameters to improve maintainability.

+/**
+ * Measures text without word wrapping.
+ * @param renderer - The text renderer or Text component
+ * @param rendererHeight - The height of the renderer
+ * @param lineSpacing - The spacing between lines
+ * @returns TextMetrics containing the measured text dimensions and line information
+ */
 static measureTextWithoutWrap(
packages/core/src/ui/UICanvas.ts (1)

349-351: Consider using type predicates for safer type casting.

The current type casting could be made safer by using TypeScript type predicates:

function isUIGraphics(component: Component): component is IUIGraphics {
  return component._componentType === ComponentType.UIRenderer;
}

Then update the code:

-      if (component.enabled && component._componentType === ComponentType.UIRenderer) {
-        (component as unknown as IUIGraphics).depth = depth;
-        elements[depth] = component as unknown as IUIGraphics;
+      if (component.enabled && isUIGraphics(component)) {
+        component.depth = depth;
+        elements[depth] = component;
packages/core/src/2d/text/TextRenderer.ts (3)

Line range hint 66-74: Optimize color setter to avoid unnecessary updates.

The color setter should check individual components to avoid triggering unnecessary updates when the same color values are set.

set color(value: Color) {
-  if (this._color !== value) {
+  if (this._color.r !== value.r || 
+      this._color.g !== value.g || 
+      this._color.b !== value.b || 
+      this._color.a !== value.a) {
    this._color.copyFrom(value);
  }
}

Line range hint 473-616: Refactor _updateLocalData for better maintainability.

This method is quite complex and handles multiple responsibilities. Consider breaking it down into smaller, focused methods:

  • Text measurement
  • Layout calculation
  • Chunk management

Suggested structure:

private _updateLocalData(): void {
  if (!this._calculateTextMetrics()) {
    return;
  }
  
  this._calculateLayout();
  this._updateChunks();
  this._setDirtyFlagFalse(RendererUpdateFlags.LocalPositionAndBounds);
}

private _calculateTextMetrics(): boolean {
  // Text measurement logic
}

private _calculateLayout(): void {
  // Layout calculation logic
}

private _updateChunks(): void {
  // Chunk management logic
}

694-704: Consider explicit enum inheritance for clarity.

While the enum extends RendererUpdateFlags as per the comment, consider making this relationship more explicit in the code.

enum TextRendererUpdateFlags {
  // Base flags from RendererUpdateFlags
  LocalPosition = RendererUpdateFlags.LocalPosition,
  WorldPosition = RendererUpdateFlags.WorldPosition,
  LocalBounds = RendererUpdateFlags.LocalBounds,
  WorldBounds = RendererUpdateFlags.WorldBounds,
  
  // Text-specific flags
  SubFont = 0x10,
  Color = 0x20,

  FontAllPositionAndBounds = SubFont | LocalPosition | WorldPosition | LocalBounds | WorldBounds,
  All = FontAllPositionAndBounds | Color
}
packages/core/src/Entity.ts (3)

107-110: Consider adding transform setter validation.

While the getter is correctly implemented, consider adding a setter with validation to ensure the transform's integrity, especially since it's a critical component for entity positioning.

 get transform(): Transform {
   return this._transform;
 }
+set transform(value: Transform) {
+  if (!value) {
+    throw new Error("Transform cannot be null or undefined");
+  }
+  this._transform = value;
+}

790-797: Document EntityModifyFlags enum values.

The new enum lacks documentation for its values, which is important for maintainability.

 export enum EntityModifyFlags {
+  /** Flag indicating parent entity has changed */
   Parent = 0x1,
+  /** Flag indicating sibling index has changed */
   SiblingIndex = 0x2,
+  /** Flag indicating UI canvas was enabled in scene */
   UICanvasEnableInScene = 0x4,
+  /** Flag indicating UI canvas was disabled in scene */
   UICanvasDisableInScene = 0x8,
+  /** Flag indicating UI group was enabled in scene */
   UIGroupEnableInScene = 0x10,
+  /** Flag indicating UI group was disabled in scene */
   UIGroupDisableInScene = 0x20
 }

783-783: Consider caching matrix inversion.

Matrix inversion is computationally expensive. Consider caching the result if this method is called frequently.

 if (this._inverseWorldMatFlag.flag) {
+  // Cache the previous matrix for comparison
+  const prevMatrix = this._transform.worldMatrix.clone();
   Matrix.invert(this._transform.worldMatrix, this._invModelMatrix);
   this._inverseWorldMatFlag.flag = false;
+  // Store the matrix used for inversion to detect changes
+  this._lastInvertedMatrix = prevMatrix;
 }
packages/core/src/input/pointer/emitter/PointerUIEventEmitter.ts (2)

168-190: Consider refactoring duplicated path comparison logic

The logic for comparing paths to find common ancestors is duplicated in multiple methods (_updateRaycast, _processUp, and _processLeave). Refactoring this code into a helper method would improve maintainability and reduce code duplication.

Do you want me to generate the refactored code or open a GitHub issue to track this task?


229-229: Define magic number as a constant for better maintainability

The magic number 1024 is used to prevent infinite loops by limiting the iteration count. Defining this number as a constant with a descriptive name enhances code readability and makes future adjustments easier.

Apply this diff to define the magic number as a constant:

-        for (; i < 1024 && !!entity && entity !== rootEntity; i++) {
+        const MAX_PATH_DEPTH = 1024;
+        for (; i < MAX_PATH_DEPTH && !!entity && entity !== rootEntity; i++) {
packages/core/src/ui/Image.ts (1)

21-23: Consider moving static properties to the end of the class.

To improve readability and follow common conventions, consider moving the static properties _tempVec2 and _tempUnit8Array to the end of the class definition.

packages/core/src/ui/Text.ts (2)

225-225: Avoid using assignment expressions.

Using assignments within expressions can be confusing and is often considered a code smell. It's better to separate the assignment from the expression for clarity and maintainability.

Refactor the code to avoid assignment expressions. For example:

-    this._subFont && (this._subFont = null);
+    if (this._subFont) {
+        this._subFont = null;
+    }

Apply similar changes to the other instances of assignment expressions.

Also applies to: 466-467, 477-477, 479-479, 566-566, 568-568

🧰 Tools
🪛 Biome

[error] 225-225: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


612-616: Consider making the TextChunk class properties readonly.

Since the TextChunk class is an internal data structure used by the Text class, consider making its properties readonly to prevent accidental modifications from outside the class.

Apply the following changes:

 class TextChunk {
-  charRenderInfos = new Array<CharRenderInfo>();
+  readonly charRenderInfos = new Array<CharRenderInfo>();
-  subChunk: SubPrimitiveChunk;
+  readonly subChunk: SubPrimitiveChunk;
-  texture: Texture2D;
+  readonly texture: Texture2D;
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 4b2487d and 8094d8a.

📒 Files selected for processing (17)
  • packages/core/src/2d/text/TextRenderer.ts (20 hunks)
  • packages/core/src/2d/text/TextUtils.ts (11 hunks)
  • packages/core/src/Entity.ts (10 hunks)
  • packages/core/src/input/pointer/emitter/PointerUIEventEmitter.ts (1 hunks)
  • packages/core/src/ui/Image.ts (1 hunks)
  • packages/core/src/ui/Text.ts (1 hunks)
  • packages/core/src/ui/UICanvas.ts (1 hunks)
  • packages/core/src/ui/UIGroup.ts (1 hunks)
  • packages/core/src/ui/UIRenderer.ts (1 hunks)
  • packages/core/src/ui/UIUtils.ts (1 hunks)
  • packages/core/src/ui/index.ts (1 hunks)
  • packages/core/src/ui/interactive/UIInteractive.ts (1 hunks)
  • packages/core/src/ui/interactive/transition/SpriteTransition.ts (1 hunks)
  • packages/core/src/ui/interactive/transition/Transition.ts (1 hunks)
  • packages/core/src/ui/interface/IUIElement.ts (1 hunks)
  • packages/core/src/ui/interface/IUIGraphics.ts (1 hunks)
  • packages/core/src/ui/interface/IUIGroupable.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/core/src/ui/interactive/transition/SpriteTransition.ts
  • packages/core/src/ui/interactive/transition/Transition.ts
  • packages/core/src/ui/interface/IUIGraphics.ts
🧰 Additional context used
🪛 Biome
packages/core/src/Entity.ts

[error] 612-612: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/input/pointer/emitter/PointerUIEventEmitter.ts

[error] 85-85: The function should not return a value because its return type is void.

The function is here:

'void' signals the absence of value. The returned value is likely to be ignored by the caller.

(lint/correctness/noVoidTypeReturn)


[error] 102-102: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 102-102: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 222-222: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/ui/Text.ts

[error] 225-225: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 466-466: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 467-467: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 477-477: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 479-479: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 552-552: This is an unexpected use of the debugger statement.

Unsafe fix: Remove debugger statement

(lint/suspicious/noDebugger)


[error] 566-566: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 568-568: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/ui/UICanvas.ts

[error] 194-194: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 211-211: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 357-357: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 471-472: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 481-482: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 483-485: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

packages/core/src/ui/UIGroup.ts

[error] 105-105: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)


[error] 110-110: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)


[error] 153-153: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/ui/UIRenderer.ts

[error] 158-158: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 162-162: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 163-163: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 161-165: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

packages/core/src/ui/UIUtils.ts

[error] 16-153: Avoid classes that contain only static members.

Prefer using simple functions instead of classes with only static members.

(lint/complexity/noStaticOnlyClass)


[error] 53-53: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 72-72: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 130-130: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 130-130: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 131-131: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 131-131: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 132-132: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 132-132: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)

packages/core/src/ui/interactive/UIInteractive.ts

[error] 132-134: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

🔇 Additional comments (49)
packages/core/src/ui/interface/IUIGroupable.ts (2)

1-2: LGTM! Imports are well-organized.

The imports are properly structured and use appropriate relative paths.


4-9: Verify the impact of interface changes across the codebase.

Since this is a new interface that will be implemented by UI components, we should verify its usage pattern across the codebase.

✅ Verification successful

Let me check the actual implementations and usages to better understand the impact.


Interface changes appear to be properly integrated across the codebase

The interface IUIGroupable is well-integrated into the UI component hierarchy:

  • Correctly implemented by UIInteractive class
  • Used as a base interface for IUIGraphics
  • Properly utilized in UIGroup for managing grouped elements
  • Integrated with UIUtils for group management functionality

The interface changes maintain consistency with its implementations and usages across the codebase. All required properties and methods are properly referenced.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for potential implementations and usages of IUIGroupable
echo "Searching for implementations of IUIGroupable..."
rg "implements.*IUIGroupable" -l

echo "\nSearching for imports of IUIGroupable..."
rg "import.*IUIGroupable" -l

echo "\nSearching for type references to IUIGroupable..."
rg ": IUIGroupable" -l

Length of output: 645


Script:

#!/bin/bash
# Check the implementation in UIInteractive
echo "Checking UIInteractive implementation..."
rg "implements.*IUIGroupable" -A 10 packages/core/src/ui/interactive/UIInteractive.ts

echo "\nChecking UIGroup usage of IUIGroupable..."
rg "IUIGroupable" -A 5 packages/core/src/ui/UIGroup.ts

echo "\nChecking UIUtils usage..."
rg "IUIGroupable" -A 5 packages/core/src/ui/UIUtils.ts

echo "\nChecking IUIGraphics interface..."
rg "IUIGroupable" -A 5 packages/core/src/ui/interface/IUIGraphics.ts

Length of output: 1924

packages/core/src/ui/interface/IUIElement.ts (1)

1-3: LGTM!

The imports are correct and necessary for the interface implementation.

packages/core/src/ui/index.ts (3)

1-4: LGTM! Well-organized core exports.

The grouping of core UI components and enums at the top of the file follows good organizational practices. The separation of enums into their own directory suggests good code organization.


1-15: Consider adding type exports for public interfaces.

While the component exports are well-organized, consider also exporting any public interfaces or types that consumers might need when working with these UI components.

Let's check for potentially missing type exports:

#!/bin/bash
# Description: Look for interfaces that might need to be exported

# Search for public interfaces in UI-related files
echo "Checking for public interfaces in UI files..."
rg "export interface" "./packages/core/src/ui/"

12-15: Verify transition system integration.

The transition system appears to be comprehensive, covering color, scale, and sprite transitions. However, we should verify that these transitions are properly integrated with the UI components.

Let's verify the integration:

✅ Verification successful

Transition system is properly integrated with UI components

The verification shows a well-structured transition system:

  • UIInteractive class serves as the integration point, managing transitions through _transitions array
  • Base Transition class is properly extended by ColorTransition, ScaleTransition, and SpriteTransition
  • Transitions are properly managed with methods like getTransition, addTransition, removeTransition
  • Transitions are automatically updated in the component lifecycle through onUpdate
  • State changes are properly propagated to all transitions via _setState

The implementation shows a robust and type-safe transition system that's tightly integrated with the UI component lifecycle.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if transitions are properly integrated with UI components

# Test 1: Check for transition usage in UI components
echo "Checking transition usage in UI components..."
rg -l "Transition" "./packages/core/src/ui/(Button|Image|Text).ts"

# Test 2: Check if Transition is the base class for other transitions
echo "Verifying transition hierarchy..."
ast-grep --pattern 'class $_ extends Transition'

Length of output: 666


Script:

#!/bin/bash
# Let's verify the transition system integration with more precise commands

# Check for transition imports and usage in UI components
echo "Checking transition usage in UI components..."
rg -l "import.*Transition" packages/core/src/ui/

# Check implementation of transition classes
echo "Checking transition implementations..."
rg -A 5 "class.*Transition" packages/core/src/ui/interactive/transition/

# Check if UI components have transition-related methods or properties
echo "Checking UI components for transition methods..."
rg -A 3 "(transition|animate)" packages/core/src/ui/

Length of output: 6692

packages/core/src/ui/interactive/UIInteractive.ts (6)

1-36: LGTM! Well-structured class definition with clear property organization.

The class structure follows good practices with appropriate use of decorators and clear property visibility modifiers.


52-78: LGTM! Well-implemented transition management with type safety.

The transition management methods make good use of TypeScript generics and provide a clean API for managing transitions.


80-102: LGTM! Clean event handling implementation.

The pointer event handlers maintain a clear separation between state tracking and updates.


107-123: LGTM! Proper lifecycle management with clean state handling.

The lifecycle methods properly manage registration/unregistration and state cleanup.


157-174: LGTM! Clean state management implementation.

The state management methods are well-organized with clear transition logic and state calculations.


130-140: ⚠️ Potential issue

Fix switch case fallthrough issue.

The cases for UICanvasEnableInScene and Parent are falling through to UIGroupEnableInScene unintentionally.

🧰 Tools
🪛 Biome

[error] 132-134: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

packages/core/src/ui/UIGroup.ts (5)

1-41: LGTM! Well-structured class definition with proper property management.

The class structure is well-organized with appropriate use of decorators and clear documentation of internal properties.


43-75: LGTM! Well-implemented property accessors.

The getters and setters are properly implemented with appropriate validation and update triggers.


117-144: LGTM! Thorough lifecycle management.

The lifecycle methods properly handle registration, cleanup, and garbage collection.


186-191: LGTM! Well-designed flags enum.

The GroupModifyFlags enum is properly implemented using bit flags, allowing efficient combination of multiple flags.


153-154: 🛠️ Refactor suggestion

Improve readability by avoiding assignment in expression.

The assignment within the logical AND expression makes the code harder to read and understand.

Apply this diff:

- replaced && (replaced._groupIndex = this._groupIndex);
+ if (replaced) {
+   replaced._groupIndex = this._groupIndex;
+ }

Likely invalid or redundant comment.

🧰 Tools
🪛 Biome

[error] 153-153: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/ui/UIRenderer.ts (1)

19-20: ⚠️ Potential issue

Add missing ISpriteRenderer interface.

The class should implement the ISpriteRenderer interface to maintain consistency with the sprite rendering system.

-export abstract class UIRenderer extends Renderer implements IUIGraphics {
+export abstract class UIRenderer extends Renderer implements IUIGraphics, ISpriteRenderer {

Likely invalid or redundant comment.

packages/core/src/2d/text/TextUtils.ts (2)

2-2: LGTM: Import addition aligns with new type requirements.

The addition of the Text import supports the new union type parameter (TextRenderer | Text) in the measure methods.


99-104: Verify interface compatibility between TextRenderer and Text.

Both measure methods accept either TextRenderer or Text type. Let's verify they implement the required interface members.

Also applies to: 276-280

✅ Verification successful

Let me gather more information about both TextRenderer and Text classes to verify their interface compatibility.


Let me search for more specific information about the text measurement methods and required interface members.


Let me search for the required properties and methods in both classes with a different pattern.


Based on the gathered information, I can now provide the final response:

Interface compatibility between TextRenderer and Text is verified.

Both TextRenderer and Text classes implement the required interface members for measureTextWithWrap:

  • Both have overflowMode property with identical behavior (defaults to OverflowMode.Overflow)
  • Both have width and height properties
  • Both use the same text measurement logic through TextUtils.measureTextWithWrap
  • Both handle text overflow and truncation in the same way

The shared interface is consistently implemented across both classes, making them safely interchangeable in the measureTextWithWrap method.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for the Text class definition to verify required members
echo "Searching for Text class definition..."
ast-grep --pattern 'class Text {
  $$$
  _getSubFont() {
    $$$
  }
  $$$
}'

echo "Verifying overflowMode property..."
ast-grep --pattern 'class Text {
  $$$
  overflowMode: $_
  $$$
}'

Length of output: 314


Script:

#!/bin/bash
# Search for TextRenderer interface/class definition
echo "Searching for TextRenderer definition..."
rg "(?:interface|class|type)\s+TextRenderer" -A 10

# Search for Text class usage of measureTextWithWrap
echo "Searching for Text class measureText related methods..."
rg "class Text" -A 30

# Search for common method signatures between Text and TextRenderer
echo "Searching for common method signatures..."
ast-grep --pattern '(class Text|class TextRenderer) {
  $$$
  measureText($_) {
    $$$
  }
  $$$
}'

Length of output: 36998


Script:

#!/bin/bash
# Search for measureTextWithWrap implementation and usage
echo "Searching for measureTextWithWrap implementation..."
rg "measureTextWithWrap" -A 5

# Search for required properties and methods in both classes
echo "Searching for text measurement related properties..."
ast-grep --pattern '(class Text|class TextRenderer) {
  $$$
  _getSubFont() {
    $$$
  }
  $$$
  overflowMode: $_
  $$$
}'

# Search for common interface or type definitions
echo "Searching for TextRenderer interface..."
rg "interface.*TextRenderer" -A 10

Length of output: 8882


Script:

#!/bin/bash
# Search for overflowMode property in both classes
echo "Searching for overflowMode property..."
rg "overflowMode.*=" -B 5 -A 1

# Search for _getSubFont method in both classes
echo "Searching for _getSubFont method..."
rg "_getSubFont.*{" -B 2 -A 10

# Search for width and height properties
echo "Searching for width and height properties..."
rg "(get width\(\)|get height\(\))" -B 2 -A 5

Length of output: 18444

packages/core/src/2d/text/TextRenderer.ts (1)

35-35: ⚠️ Potential issue

Fix array initialization syntax.

The array initialization syntax is incorrect. Using Array<TextChunk>() without the new keyword doesn't create a new array instance.

-private _textChunks = Array<TextChunk>();
+private _textChunks: TextChunk[] = [];

Likely invalid or redundant comment.

packages/core/src/Entity.ts (4)

99-105: LGTM: Property refactoring improves encapsulation.

The refactoring of transform to a private _transform property with a getter improves encapsulation. The addition of _updateFlagManager enables better state change management.


205-206: LGTM: Constructor initialization is correct.

The initialization of _transform and registration of world change flag is properly implemented.


527-530: LGTM: Proper cleanup in destroy method.

The cleanup of _updateFlagManager is correctly implemented with null checks and removal of all listeners.


404-404: Verify UI Transform inheritance.

The conditional addition of UITransform needs verification to ensure proper inheritance across the entity hierarchy.

✅ Verification successful

UI Transform inheritance is correctly implemented

The verification shows that the UI Transform inheritance is properly handled:

  1. UITransform extends the base Transform class and is automatically added to components that require it through the @dependentComponents decorator (seen in UIRenderer and UICanvas).
  2. The inheritance check in Entity.createChild() ensures that if a parent entity has a UITransform, its children will also get a UITransform component.
  3. The implementation maintains consistency in the UI component hierarchy by propagating the appropriate transform type from parent to child entities.
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check UITransform usage patterns
# Expected: Consistent UITransform inheritance in parent-child relationships

# Check UITransform inheritance patterns
ast-grep --pattern 'class $_ extends $_ {
  $$$
  addComponent($_) {
    $$$
    UITransform
    $$$
  }
  $$$
}'

Length of output: 115


Script:

#!/bin/bash
# Let's try a different approach to analyze UITransform usage

# Search for UITransform related code patterns
rg "UITransform" -A 5 -B 5

# Also check for specific inheritance patterns
ast-grep --pattern 'this._transform instanceof UITransform'

# Look for component addition patterns
ast-grep --pattern 'addComponent($_)'

Length of output: 21016

packages/core/src/input/pointer/emitter/PointerUIEventEmitter.ts (3)

102-102: ****

🧰 Tools
🪛 Biome

[error] 102-102: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 102-102: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


222-222: ****

🧰 Tools
🪛 Biome

[error] 222-222: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


167-168: ****

packages/core/src/ui/Image.ts (19)

1-19: LGTM!

The imports are well-organized and cover the necessary dependencies for the Image class implementation.


20-20: LGTM!

The class declaration is correct, extending UIRenderer and implementing the ISpriteRenderer interface.


24-35: LGTM!

The private properties are well-defined, with appropriate types and default values. The use of @ignoreClone and @assignmentClone decorators is consistent.


37-43: LGTM!

The getter and setter for alphaHitTestMinimumThreshold are implemented correctly, with proper clamping of the input value.


45-71: LGTM!

The getter and setter for drawMode are implemented correctly. The setter updates the _assembler based on the selected draw mode and resets the sprite data.


73-87: LGTM!

The getter and setter for tileMode are implemented correctly. The setter updates the dirty flag when the draw mode is set to Tiled.


89-104: LGTM!

The getter and setter for tiledAdaptiveThreshold are implemented correctly, with proper clamping of the input value and updating the dirty flag when the draw mode is set to Tiled.


106-130: LGTM!

The getter and setter for sprite are implemented correctly. The setter handles resource reference counting, event listener management, and updating the shader data.


132-143: LGTM!

The constructor initializes the necessary properties and binds event listeners correctly.


145-178: LGTM!

The _hitTest method is implemented correctly, handling the hit testing based on the sprite's bounds and alpha threshold.


180-191: LGTM!

The _updateLocalBounds method is implemented correctly, updating the local bounds based on the sprite's size and pivot.


193-248: LGTM!

The _render method is implemented correctly, handling the rendering process based on the sprite's properties and updating the necessary data when dirty flags are set.


250-255: LGTM!

The _canBatch method is implemented correctly, delegating the batching decision to BatchUtils.canBatchSprite.


257-262: LGTM!

The _batch method is implemented correctly, delegating the batching process to BatchUtils.batchFor2D.


264-283: LGTM!

The _onTransformChanged method is implemented correctly, updating the dirty flags based on the transform modification type.


285-298: LGTM!

The _onDestroy method is implemented correctly, handling resource cleanup and event listener removal.


300-341: LGTM!

The _onSpriteChange method is implemented correctly, updating the dirty flags based on the sprite modification type.


343-356: LGTM!

The _getUVByLocalPosition method is implemented correctly, delegating the UV calculation to the appropriate sprite assembler based on the draw mode.


359-371: LGTM!

The ImageUpdateFlags enum is defined correctly, extending the UIRendererUpdateFlags and providing additional flags specific to the Image class.

packages/core/src/ui/Text.ts (2)

25-609: The Text class implementation looks solid and comprehensive. Great work!

The class provides a robust set of properties and methods for managing text rendering in a UI context. The code is well-structured, follows best practices, and includes appropriate optimizations for efficient rendering.

Some key highlights:

  • Proper handling of resource management through reference counting
  • Efficient rendering using text chunks and sub-render elements
  • Comprehensive text layout and alignment options
  • Effective use of dirty flags for updating rendering state
  • Well-organized and readable code structure

Overall, the Text class seems to be a valuable addition to the UI rendering system.

🧰 Tools
🪛 Biome

[error] 225-225: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 466-466: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 467-467: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 477-477: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 479-479: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 552-552: This is an unexpected use of the debugger statement.

Unsafe fix: Remove debugger statement

(lint/suspicious/noDebugger)


[error] 566-566: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 568-568: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


1-628: Verify the integration of the Text class with other UI components.

The Text class seems to be well-integrated with the UI rendering system, particularly with the UIRenderer class. However, it's important to ensure that the Text class works seamlessly with other UI components introduced in this pull request, such as UIGroup and UICanvas.

To verify the integration, you can perform the following checks:

  1. Review the usage of the Text class in the context of UIGroup and UICanvas classes to ensure proper rendering order and hierarchy management.

  2. Analyze the interaction between the Text class and the enhanced ComponentsManager to confirm that text rendering is efficiently handled within the overall UI hierarchy.

  3. Test the rendering of Text instances in various scenarios, such as different canvas render modes, to validate the expected behavior.

  4. Verify that the Text class correctly utilizes the rendering optimizations provided by the UIRenderer class, such as rendering contexts and batching.

If any issues or inconsistencies are found during the verification process, please let me know, and I'll be happy to assist in resolving them.

🧰 Tools
🪛 Biome

[error] 225-225: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 466-466: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 467-467: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 477-477: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 479-479: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 552-552: This is an unexpected use of the debugger statement.

Unsafe fix: Remove debugger statement

(lint/suspicious/noDebugger)


[error] 566-566: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 568-568: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

Comment on lines 4 to 6
export interface IUIGroupable extends IUIElement {
_group: UIGroup;
_indexInGroup: number;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider revising the interface design to hide implementation details.

The current design exposes internal implementation details through properties prefixed with underscores. This approach:

  1. Breaks encapsulation by exposing internal state
  2. Makes the interface tightly coupled to the implementation
  3. Deviates from TypeScript interface conventions

Consider this alternative design that hides implementation details:

-export interface IUIGroupable extends IUIElement {
-  _group: UIGroup;
-  _indexInGroup: number;
+export interface IUIGroupable extends IUIElement {
+  readonly group: UIGroup;
+  readonly indexInGroup: number;

This change:

  • Removes implementation-specific underscore prefixes
  • Makes properties readonly to prevent external modifications
  • Better adheres to interface design principles
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export interface IUIGroupable extends IUIElement {
_group: UIGroup;
_indexInGroup: number;
export interface IUIGroupable extends IUIElement {
readonly group: UIGroup;
readonly indexInGroup: number;

Comment on lines 47 to 82
static registerElementToCanvas(element: IUIElement, canvas: UICanvas): void {
const preCanvas = element._rootCanvas;
if (preCanvas !== canvas) {
element._rootCanvas = canvas;
if (preCanvas) {
const replaced = preCanvas._disorderedElements.deleteByIndex(element._indexInCanvas);
replaced && (replaced._indexInCanvas = element._indexInCanvas);
element._indexInCanvas = -1;
preCanvas._hierarchyDirty = true;
}
if (canvas) {
const disorderedElements = canvas._disorderedElements;
element._indexInCanvas = disorderedElements.length;
disorderedElements.add(element);
canvas._hierarchyDirty = true;
}
}
}

static registerElementToGroup(element: IUIGroupable, group: UIGroup): void {
const preGroup = element._group;
if (preGroup !== group) {
element._group = group;
if (preGroup) {
const replaced = preGroup._disorderedElements.deleteByIndex(element._indexInGroup);
replaced && (replaced._indexInGroup = element._indexInGroup);
element._indexInGroup = -1;
}
if (group) {
const disorderedElements = group._disorderedElements;
element._indexInGroup = disorderedElements.length;
disorderedElements.add(element);
}
element._onGroupModify(GroupModifyFlags.All);
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Reduce code duplication in registration methods.

The registerElementToCanvas and registerElementToGroup methods share similar logic. Consider extracting the common functionality into a private helper method:

private static registerElementToContainer<T extends IUIElement, C>(
  element: T,
  container: C,
  getPreContainer: (el: T) => C,
  setContainer: (el: T, c: C) => void,
  getDisorderedElements: (c: C) => DisorderedArray<T>,
  getIndex: (el: T) => number,
  setIndex: (el: T, i: number) => void,
  onModify?: (el: T) => void
): void {
  const preContainer = getPreContainer(element);
  if (preContainer !== container) {
    setContainer(element, container);
    if (preContainer) {
      const replaced = getDisorderedElements(preContainer).deleteByIndex(getIndex(element));
      if (replaced) {
        setIndex(replaced, getIndex(element));
      }
      setIndex(element, -1);
    }
    if (container) {
      const disorderedElements = getDisorderedElements(container);
      setIndex(element, disorderedElements.length);
      disorderedElements.add(element);
    }
    onModify?.(element);
  }
}
🧰 Tools
🪛 Biome

[error] 53-53: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 72-72: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

Comment on lines 84 to 126
static getRootCanvasInParent(entity: Entity): UICanvas {
while (entity) {
const components = entity._components;
for (let i = 0, n = components.length; i < n; i++) {
const component = components[i];
if (
component.enabled &&
component._componentType === ComponentType.UICanvas &&
(<UICanvas>component)._isRootCanvas
) {
return <UICanvas>component;
}
}
entity = entity.parent;
}
return null;
}

static getGroupInParents(entity: Entity): UIGroup {
let meetRootCanvas = false;
while (entity) {
const components = entity._components;
for (let i = 0, n = components.length; i < n; i++) {
const component = components[i];
if (component.enabled) {
switch (component._componentType) {
case ComponentType.UICanvas:
meetRootCanvas = (<UICanvas>component)._isRootCanvas;
break;
case ComponentType.UIGroup:
return <UIGroup>component;
default:
break;
}
}
}
if (meetRootCanvas) {
return null;
}
entity = entity.parent;
}
return null;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve type safety and performance in traversal methods.

  1. Replace type casting with type guards for better type safety.
  2. Consider caching results for frequently accessed entities.
private static isUICanvas(component: any): component is UICanvas {
  return component._componentType === ComponentType.UICanvas;
}

private static isUIGroup(component: any): component is UIGroup {
  return component._componentType === ComponentType.UIGroup;
}

static getRootCanvasInParent(entity: Entity): UICanvas {
  // Consider adding WeakMap cache for frequent lookups
  private static canvasCache = new WeakMap<Entity, UICanvas>();
  
  if (UIUtils.canvasCache.has(entity)) {
    return UIUtils.canvasCache.get(entity);
  }
  
  while (entity) {
    const components = entity._components;
    for (const component of components) {
      if (component.enabled && UIUtils.isUICanvas(component) && component._isRootCanvas) {
        UIUtils.canvasCache.set(entity, component);
        return component;
      }
    }
    entity = entity.parent;
  }
  return null;
}

Comment on lines +17 to +20
private static _renderQueue: RenderQueue;
private static _virtualCamera: VirtualCamera;
private static _viewport: Vector4;

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Initialize static fields immediately to prevent race conditions.

Lazy initialization in static fields can lead to race conditions in concurrent scenarios. Consider initializing all fields during class definition:

-  private static _renderQueue: RenderQueue;
-  private static _virtualCamera: VirtualCamera;
-  private static _viewport: Vector4;
+  private static _renderQueue: RenderQueue = new RenderQueue(RenderQueueType.Transparent);
+  private static _virtualCamera: VirtualCamera = new VirtualCamera();
+  private static _viewport: Vector4 = new Vector4(0, 0, 1, 1);

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 21 to 45
static registerEntityListener(element: IUIElement): void {
const parents = element._parents;
const root = element._rootCanvas?.entity;
let entity = element._entity;
let index = 0;
while (entity && entity !== root) {
const preParent = parents[index];
if (preParent !== entity) {
preParent?._unRegisterModifyListener(element._onEntityModify);
parents[index] = entity;
entity._registerModifyListener(element._onEntityModify);
}
entity = entity.parent;
index++;
}
parents.length = index;
}

static unRegisterEntityListener(element: IUIElement): void {
const { _parents: parents } = element;
for (let i = 0, n = parents.length; i < n; i++) {
parents[i]._unRegisterModifyListener(element._onEntityModify);
}
parents.length = 0;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add input validation and error handling.

The entity listener methods lack input validation and error handling. Consider adding checks for null/undefined parameters and handling edge cases:

 static registerEntityListener(element: IUIElement): void {
+  if (!element) {
+    throw new Error("Element cannot be null or undefined");
+  }
   const parents = element._parents;
+  if (!parents) {
+    throw new Error("Element parents array is not initialized");
+  }
   // ... rest of the method
 }

 static unRegisterEntityListener(element: IUIElement): void {
+  if (!element || !element._parents) {
+    return; // Silent fail on cleanup
+  }
   const { _parents: parents } = element;
   // ... rest of the method
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
static registerEntityListener(element: IUIElement): void {
const parents = element._parents;
const root = element._rootCanvas?.entity;
let entity = element._entity;
let index = 0;
while (entity && entity !== root) {
const preParent = parents[index];
if (preParent !== entity) {
preParent?._unRegisterModifyListener(element._onEntityModify);
parents[index] = entity;
entity._registerModifyListener(element._onEntityModify);
}
entity = entity.parent;
index++;
}
parents.length = index;
}
static unRegisterEntityListener(element: IUIElement): void {
const { _parents: parents } = element;
for (let i = 0, n = parents.length; i < n; i++) {
parents[i]._unRegisterModifyListener(element._onEntityModify);
}
parents.length = 0;
}
static registerEntityListener(element: IUIElement): void {
if (!element) {
throw new Error("Element cannot be null or undefined");
}
const parents = element._parents;
if (!parents) {
throw new Error("Element parents array is not initialized");
}
const root = element._rootCanvas?.entity;
let entity = element._entity;
let index = 0;
while (entity && entity !== root) {
const preParent = parents[index];
if (preParent !== entity) {
preParent?._unRegisterModifyListener(element._onEntityModify);
parents[index] = entity;
entity._registerModifyListener(element._onEntityModify);
}
entity = entity.parent;
index++;
}
parents.length = index;
}
static unRegisterEntityListener(element: IUIElement): void {
if (!element || !element._parents) {
return; // Silent fail on cleanup
}
const { _parents: parents } = element;
for (let i = 0, n = parents.length; i < n; i++) {
parents[i]._unRegisterModifyListener(element._onEntityModify);
}
parents.length = 0;
}

Comment on lines +611 to +613
_registerModifyListener(onChange: (flag: EntityModifyFlags) => void): void {
(this._updateFlagManager ||= new UpdateFlagManager()).addListener(onChange);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Fix assignment in expression.

The assignment within the expression can be confusing and should be split for better readability.

-    (this._updateFlagManager ||= new UpdateFlagManager()).addListener(onChange);
+    if (!this._updateFlagManager) {
+      this._updateFlagManager = new UpdateFlagManager();
+    }
+    this._updateFlagManager.addListener(onChange);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
_registerModifyListener(onChange: (flag: EntityModifyFlags) => void): void {
(this._updateFlagManager ||= new UpdateFlagManager()).addListener(onChange);
}
_registerModifyListener(onChange: (EntityModifyFlags) => void): void {
if (!this._updateFlagManager) {
this._updateFlagManager = new UpdateFlagManager();
}
this._updateFlagManager.addListener(onChange);
}
🧰 Tools
🪛 Biome

[error] 612-612: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

Comment on lines 22 to 86
override _processRaycast(scenes: readonly Scene[], pointer: Pointer): void {
const { _tempRay: ray } = PointerEventEmitter;
const hitResult = this._hitResult;
const { position } = pointer;
const { x, y } = position;
for (let i = scenes.length - 1; i >= 0; i--) {
const scene = scenes[i];
if (!scene.isActive || scene.destroyed) continue;
const { _componentsManager: componentsManager } = scene;

/** Overlay Canvas */
let canvasElements = componentsManager._overlayCanvases;
ray.origin.set(position.x, position.y, 1);
ray.direction.set(0, 0, -1);
for (let j = canvasElements.length - 1; j >= 0; j--) {
if (canvasElements.get(j).raycast(ray, hitResult)) {
this._updateRaycast(<Component>hitResult.component, pointer);
return;
}
}

const cameras = componentsManager._activeCameras;
for (let j = cameras.length - 1; j >= 0; j--) {
const camera = cameras.get(j);
if (camera.renderTarget) continue;
const { pixelViewport } = camera;
if (
x < pixelViewport.x ||
y < pixelViewport.y ||
x > pixelViewport.x + pixelViewport.width ||
y > pixelViewport.y + pixelViewport.height
) {
continue;
}
camera.screenPointToRay(pointer.position, ray);

/** Other canvases */
const cameraPosition = camera.entity.transform.position;
/** Sort by rendering order */
canvasElements = componentsManager._canvases;
for (let k = 0, n = canvasElements.length; k < n; k++) {
canvasElements.get(k)._updateSortDistance(cameraPosition);
}
canvasElements.sort((a, b) => a.sortOrder - b.sortOrder || a._sortDistance - b._sortDistance);
for (let k = 0, n = canvasElements.length; k < n; k++) {
canvasElements.get(k)._canvasIndex = k;
}
const farClipPlane = camera.farClipPlane;
/** Post-rendering first detection */
for (let k = 0, n = canvasElements.length; k < n; k++) {
const canvas = canvasElements.get(k);
if (canvas.renderCamera !== camera) continue;
if (canvas.raycast(ray, hitResult, farClipPlane)) {
this._updateRaycast(<Component>hitResult.component, pointer);
return;
}
}
if (camera.clearFlags & CameraClearFlags.Color) {
this._updateRaycast(null);
return;
}
}
}
return null;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Method complexity exceeds recommended threshold

The _processRaycast method is complex with multiple nested loops and conditions. Consider breaking it down into smaller, focused methods:

  • processOverlayCanvas
  • processCameraCanvas
  • handleCameraRaycast

This would improve readability and maintainability.

Do you want me to generate the refactored code or open a GitHub issue to track this task?

🧰 Tools
🪛 Biome

[error] 85-85: The function should not return a value because its return type is void.

The function is here:

'void' signals the absence of value. The returned value is likely to be ignored by the caller.

(lint/correctness/noVoidTypeReturn)

Comment on lines 88 to 89
override _processDrag(pointer: Pointer): void {
if (this._draggedElement) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add parameter validation and error handling

The _processDrag method accepts a pointer parameter without validation. Consider adding null checks and appropriate error handling to prevent runtime exceptions.

Apply this diff to add validation:

override _processDrag(pointer: Pointer): void {
+  if (!pointer) {
+    console.warn('Invalid pointer provided to _processDrag');
+    return;
+  }
  if (this._draggedElement) {

Do you want me to open a GitHub issue to track this task?

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
override _processDrag(pointer: Pointer): void {
if (this._draggedElement) {
override _processDrag(pointer: Pointer): void {
if (!pointer) {
console.warn('Invalid pointer provided to _processDrag');
return;
}
if (this._draggedElement) {

}
}
}
return null;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove unnecessary return null; in a void function

The method _processRaycast is declared with a return type of void, but it ends with return null;. Since the function is expected to return void, the return null; statement is unnecessary and should be removed to prevent confusion.

Apply this diff to fix the issue:

-    return null;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return null;
🧰 Tools
🪛 Biome

[error] 85-85: The function should not return a value because its return type is void.

The function is here:

'void' signals the absence of value. The returned value is likely to be ignored by the caller.

(lint/correctness/noVoidTypeReturn)

}

private _isTextNoVisible(): boolean {
debugger;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove the debugger statement.

The debugger statement should not be present in production code as it can pause execution and affect performance.

Remove the following line:

-    debugger;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
debugger;
🧰 Tools
🪛 Biome

[error] 552-552: This is an unexpected use of the debugger statement.

Unsafe fix: Remove debugger statement

(lint/suspicious/noDebugger)

@singlecoder singlecoder changed the title Add GUI Infrastructure Add GUI Nov 20, 2024
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments.

🛑 Comments failed to post (68)
packages/ui/src/component/UITransform.ts (2)

32-41: ⚠️ Potential issue

Avoid accessing private properties and suppressing TypeScript errors

The use of // @ts-ignore to access entity._transform and entity._inverseWorldMatFlag indicates that you're accessing private or protected properties. Suppressing TypeScript errors can hide potential issues and compromises type safety.

Consider the following actions:

  • Refactor the code to use public APIs or accessor methods: If possible, use public methods or properties provided by the Entity class to achieve the same functionality.
  • Modify the base class to expose necessary properties: If you have control over the Entity class, consider exposing _transform and _inverseWorldMatFlag through public or protected members.
  • Extend the Entity class appropriately: Create a subclass of Entity that exposes the needed properties in a type-safe manner.

46-47: ⚠️ Potential issue

Accessing private members _updateFlagManager may lead to maintenance issues

Directly accessing private members like this._updateFlagManager by suppressing TypeScript errors with // @ts-ignore can lead to maintenance challenges and violates encapsulation principles.

Consider the following solutions:

  • Expose a protected method or property in the base class: Modify the Transform class to provide protected access to _updateFlagManager.
  • Use public methods to dispatch updates: If available, use existing public methods to dispatch update flags without needing to access private members.

Also applies to: 52-53

packages/ui/src/component/interactive/transition/Transition.ts (4)

84-94: ⚠️ Potential issue

Prevent potential division by zero in set duration method

In the set duration(value: number) method, there is a potential division by zero when preDuration is zero.

At line 90:

this._countDown = value * (1 - this._countDown / preDuration);

If preDuration is zero, dividing by zero will result in Infinity or NaN, causing unexpected behavior. To prevent this, add a check to ensure preDuration is not zero before performing the division.

Apply this diff to fix the issue:

      set duration(value: number) {
        if (value < 0) value = 0;
        const preDuration = this._duration;
        if (preDuration !== value) {
          this._duration = value;
          if (this._countDown > 0) {
+           if (preDuration > 0) {
              this._countDown = value * (1 - this._countDown / preDuration);
+           } else {
+             this._countDown = value;
+           }
            this._updateValue();
          }
        }
      }

76-76: ⚠️ Potential issue

Ensure _currentValue is initialized before use

In lines 76 and 148, the method _applyValue is called with this._currentValue, which may be undefined if not properly initialized. This can lead to unexpected behavior or runtime errors.

Initialize this._currentValue before it's used. Consider setting a default value during declaration or ensuring it's assigned in all code paths before invocation:

  protected _currentValue: T;
+ 
+ constructor() {
+   // Initialize _currentValue with a default value, if appropriate
+   this._currentValue = this.normal; // or any suitable default
+ }

Alternatively, add checks before calling _applyValue:

        if (this._currentValue !== undefined) {
          value?.enabled && this._applyValue(this._currentValue);
        }

Ensuring _currentValue is properly initialized enhances code stability.

Also applies to: 148-148


135-138: 🛠️ Refactor suggestion

Eliminate the use of @ts-ignore directives

Using @ts-ignore in lines 135 and 137 suppresses TypeScript compiler errors, which can hide potential issues and reduce type safety.

Replace @ts-ignore with proper type checks or type assertions to maintain type safety. Here's how you can modify the code:

      protected _onStateValueDirty(state: InteractiveState, preValue: T, curValue: T): void {
-       // @ts-ignore
-       preValue instanceof ReferResource && preValue._addReferCount(-1);
+       if (preValue instanceof ReferResource) {
+         preValue._addReferCount(-1);
+       }
-       // @ts-ignore
-       curValue instanceof ReferResource && curValue._addReferCount(1);
+       if (curValue instanceof ReferResource) {
+         curValue._addReferCount(1);
+       }
        if (this._finalState === state) {
          this._finalValue = curValue;
          this._updateValue();
        }
      }

This approach ensures that preValue and curValue are instances of ReferResource before calling _addReferCount, improving code safety and readability.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    if (preValue instanceof ReferResource) {
      preValue._addReferCount(-1);
    }
    if (curValue instanceof ReferResource) {
      curValue._addReferCount(1);
    }

151-162: ⚠️ Potential issue

Add a default case to handle unexpected states in _getValueByState

The method _getValueByState lacks a default case in the switch statement. If an unexpected InteractiveState value is passed, the method may return undefined, leading to runtime errors.

Add a default case to ensure the method always returns a valid value:

      private _getValueByState(state: InteractiveState): T {
        switch (state) {
          case InteractiveState.Normal:
            return this.normal;
          case InteractiveState.Pressed:
            return this.pressed;
          case InteractiveState.Hover:
            return this.hover;
          case InteractiveState.Disable:
            return this.disabled;
+         default:
+           // Handle unexpected state, possibly by returning a default value or throwing an error
+           return this.normal; // or consider throwing an error
        }
      }

This change ensures that your code handles all possible cases, enhancing robustness.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  private _getValueByState(state: InteractiveState): T {
    switch (state) {
      case InteractiveState.Normal:
        return this.normal;
      case InteractiveState.Pressed:
        return this.pressed;
      case InteractiveState.Hover:
        return this.hover;
      case InteractiveState.Disable:
        return this.disabled;
      default:
        // Handle unexpected state, possibly by returning a default value or throwing an error
        return this.normal; // or consider throwing an error
    }
  }
packages/ui/src/Utils.ts (2)

79-79: 🛠️ Refactor suggestion

Avoid assignments within logical expressions for better clarity.

In lines 79, 100, 122, 133, and 135, assignments are performed within logical && expressions, which can be confusing and reduce readability.

For example, at line 79:

replaced && (replaced._indexInCanvas = element._indexInCanvas);

It's clearer to separate the assignment from the conditional check:

if (replaced) {
  replaced._indexInCanvas = element._indexInCanvas;
}

Apply similar changes to the other lines to enhance code clarity and maintainability.

Also applies to: 100-100, 122-122, 133-133, 135-135

🧰 Tools
🪛 Biome

[error] 79-79: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


18-18: 🛠️ Refactor suggestion

Minimize the use of // @ts-ignore to ensure type safety.

The code contains multiple // @ts-ignore comments (lines 18, 21, 43, 62, 87, 108), which suppress TypeScript compiler errors. Overusing // @ts-ignore can hide potential type issues and decrease code reliability. It's advisable to address the underlying type errors instead of suppressing them.

Also applies to: 21-21, 43-43, 62-62, 87-87, 108-108

packages/core/src/input/pointer/emitter/PhysicsPointerEventEmitter.ts (1)

61-61: 🛠️ Refactor suggestion

Avoid assignments within expressions for better readability

On line 61, the assignment combines multiple variables in a single expression. This can reduce code clarity and make it harder to debug.

Consider separating the assignments to enhance readability:

-const entity = (this._pressedEntity = this._draggedEntity = this._enteredEntity);
+this._pressedEntity = this._draggedEntity = this._enteredEntity;
+const entity = this._pressedEntity;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    this._pressedEntity = this._draggedEntity = this._enteredEntity;
    const entity = this._pressedEntity;
🧰 Tools
🪛 Biome

[error] 61-61: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 61-61: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/ui/src/component/UIGroup.ts (3)

148-149: ⚠️ Potential issue

Remove @ts-ignore and correctly override lifecycle methods

The use of @ts-ignore when overriding _onEnableInScene() and _onDisableInScene() suggests type mismatches. Ensure that these methods are properly declared in the base class and that your overrides match the expected signatures without suppressing type errors.

Consider updating your method names or checking the base class definitions to align with TypeScript's expectations.

Also applies to: 156-157


152-153: 🛠️ Refactor suggestion

Avoid accessing internal methods like _dispatchModify

Accessing this.entity._dispatchModify directly may break encapsulation if _dispatchModify is intended to be a private or internal method. Relying on internal APIs can lead to maintenance issues if the underlying implementation changes.

Consider using a public method or event system provided by the Entity class to dispatch modifications.


90-91: 🛠️ Refactor suggestion

Avoid using @ts-ignore; ensure _componentType assignment is type-safe

Using @ts-ignore suppresses TypeScript compiler errors and can mask underlying issues. Assigning to _componentType directly may not be safe if it's a protected or private member of the parent class. Consider providing a proper accessor or refactoring the class hierarchy to avoid bypassing TypeScript's type checking.

Apply this diff to address the issue:

-// @ts-ignore
 this._componentType = ComponentType.UIGroup;
+ (this as any)._componentType = ComponentType.UIGroup;

Or better, request an accessor from the base class if possible.

Committable suggestion skipped: line range outside the PR's diff.

packages/ui/src/component/interactive/UIInteractive.ts (3)

146-155: 🛠️ Refactor suggestion

Avoid using @ts-ignore; properly handle method overriding in _onEnableInScene.

Using @ts-ignore suppresses TypeScript errors and may conceal potential issues. The method _onEnableInScene is being overridden, but TypeScript likely flags an error due to access modifiers or method visibility in the base class Script.

Consider these options:

  • Option 1: Update the base class Script to declare _onEnableInScene as protected, allowing safe overriding.
  • Option 2: If modifying the base class is not feasible, create a new method in your subclass that does not conflict with the base class's private or internal methods.
  • Option 3: Use TypeScript's protected or public access modifiers to align with the intended visibility and override methods without suppressing errors.

Removing @ts-ignore ensures that TypeScript can properly check your code for errors.


157-167: 🛠️ Refactor suggestion

Avoid using @ts-ignore; ensure proper method overriding in _onDisableInScene.

Similar to the previous comment, suppressing TypeScript errors with @ts-ignore when overriding _onDisableInScene can hide potential issues. Properly handling method overriding is essential for code safety and maintainability.

Implement the same solutions as suggested for _onEnableInScene to address this issue.


105-109: ⚠️ Potential issue

Adjust generic type constraint in addTransition method to support constructors with parameters.

The current generic type constraint T extends new () => Transition may not accommodate Transition subclasses that require constructor arguments. This can lead to issues when adding transitions that need parameters during instantiation.

Apply this diff to adjust the generic type constraint and handle constructors with parameters:

- addTransition<T extends new () => Transition>(type: T): InstanceType<T> {
-   const transition = new type() as InstanceType<T>;
+ addTransition<T extends abstract new (...args: any[]) => Transition>(type: T, ...args: ConstructorParameters<T>): InstanceType<T> {
+   const transition = new type(...args) as InstanceType<T>;
    this._transitions.push(transition);
    transition._setState(this._state, true);
    return transition;
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    addTransition<T extends abstract new (...args: any[]) => Transition>(type: T, ...args: ConstructorParameters<T>): InstanceType<T> {
      const transition = new type(...args) as InstanceType<T>;
      this._transitions.push(transition);
      transition._setState(this._state, true);
      return transition;
    }
packages/ui/src/input/UIPointerEventEmitter.ts (3)

222-222: 🛠️ Refactor suggestion

Avoid assignments within expressions for better readability

Similar to line 103, the assignment on line 222 uses an assignment within an expression, which can reduce code clarity.

Refactor the code as follows:

-    let entity = (path[0] = element.entity);
+    path[0] = element.entity;
+    let entity = path[0];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    path[0] = element.entity;
    let entity = path[0];
🧰 Tools
🪛 Biome

[error] 222-222: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


89-89: ⚠️ Potential issue

Avoid returning a value from a function with a void return type

The processRaycast method is declared to return void, but it contains return null; on line 89. Returning a value in a void function can lead to confusion and potential type errors.

Apply this diff to correct the issue:

-    return null;
+    return;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    return;
🧰 Tools
🪛 Biome

[error] 89-89: The function should not return a value because its return type is void.

The function is here:

'void' signals the absence of value. The returned value is likely to be ignored by the caller.

(lint/correctness/noVoidTypeReturn)


103-103: 🛠️ Refactor suggestion

Avoid assignments within expressions for better readability

Assigning multiple variables within an expression can make the code harder to read and maintain. The assignment on line 103 combines several assignments in one expression.

Refactor the code as follows to improve clarity:

-    const element = (this._pressedElement = this._draggedElement = this._enteredElement);
+    this._pressedElement = this._draggedElement = this._enteredElement;
+    const element = this._pressedElement;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    this._pressedElement = this._draggedElement = this._enteredElement;
    const element = this._pressedElement;
🧰 Tools
🪛 Biome

[error] 103-103: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 103-103: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/ui/src/component/UIRenderer.ts (6)

134-135: 🛠️ Refactor suggestion

Eliminate the use of '@ts-ignore' by addressing type issues

The use of // @ts-ignore suppresses TypeScript's type checking, which may hide potential issues and reduce code maintainability. It's better to address the underlying type errors directly.

Consider modifying the base class or adjusting type declarations to eliminate the need for @ts-ignore. For example, if _componentType needs to be assigned, ensure it's properly declared in the base class or provide a setter method.


138-139: 🛠️ Refactor suggestion

Avoid accessing private properties directly

Accessing the _onValueChanged property directly may violate encapsulation principles, and using @ts-ignore to suppress the error is not ideal. Consider providing a public or protected method to handle the color change callback.

Refactor the Color class to expose a method or event for value changes, allowing you to subscribe without accessing private members.


179-182: 🛠️ Refactor suggestion

Replace '@ts-ignore' with proper type handling

The @ts-ignore directives here suppress errors related to _overrideUpdate and scene._componentsManager, possibly due to missing or incorrect type definitions.

Ensure that _overrideUpdate and scene._componentsManager are correctly defined and accessible. Update type declarations or class structures as necessary to eliminate the need for @ts-ignore.


187-190: 🛠️ Refactor suggestion

Avoid suppressing TypeScript errors with '@ts-ignore'

Similar to previous comments, using @ts-ignore hides potential issues. Address the type errors directly for better code quality.

Ensure that methods like removeOnUpdateRenderers are properly defined in scene._componentsManager and that access levels permit their use.


215-215: 🛠️ Refactor suggestion

Avoid assignments within expressions for clarity

Using assignments within expressions can make the code less readable and potentially cause confusion. Consider refactoring the code for better clarity and maintainability.

Apply this diff to improve readability:

-          rootCanvas && (rootCanvas._hierarchyDirty = true);
+          if (rootCanvas) {
+            rootCanvas._hierarchyDirty = true;
+          }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

      if (rootCanvas) {
            rootCanvas._hierarchyDirty = true;
          }
🧰 Tools
🪛 Biome

[error] 215-215: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


292-293: 🛠️ Refactor suggestion

Properly clean up event handlers without accessing private members

Setting _onValueChanged to null directly may lead to unexpected behavior, especially if _onValueChanged is a private property.

Provide a public method in the Color class to remove the event listener, ensuring encapsulation and proper cleanup.

-        //@ts-ignore
-        this._color._onValueChanged = null;
+        this._color.offValueChanged(this._onColorChange);

Committable suggestion skipped: line range outside the PR's diff.

packages/ui/src/component/advanced/Image.ts (2)

123-124: 🛠️ Refactor suggestion

Avoid repeated use of @ts-ignore; properly handle TypeScript errors

There are several instances where @ts-ignore is used to suppress TypeScript errors (lines 123, 129, 147, 150, and 284). This practice can hide potential bugs and should be avoided. Please consider properly typing the properties or methods, or refactoring the code to handle the type errors safely.

Also applies to: 129-130, 147-147, 150-150, 284-285


27-27: ⚠️ Potential issue

Fix typo in variable name _tempUnit8Array

The variable name _tempUnit8Array seems to have a typo. It should be _tempUint8Array.

Apply this diff to correct the typo:

- private static _tempUnit8Array: Uint8ClampedArray = new Uint8ClampedArray(4);
+ private static _tempUint8Array: Uint8ClampedArray = new Uint8ClampedArray(4);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  private static _tempUint8Array: Uint8ClampedArray = new Uint8ClampedArray(4);
packages/ui/src/component/advanced/Label.ts (5)

207-210: 🛠️ Refactor suggestion

Avoid using @ts-ignore; resolve TypeScript errors instead

The use of @ts-ignore suppresses TypeScript errors, which may hide underlying issues. It's better to address the root cause of the TypeScript errors to ensure type safety.

Consider updating the code to resolve the TypeScript errors without suppressing them.


456-456: 🛠️ Refactor suggestion

Avoid assignments within conditional expressions

Assigning maxY = Math.max(maxY, top) within a conditional expression can reduce code clarity.

Refactor the code as follows:

- i === firstLine && (maxY = Math.max(maxY, top));
+ if (i === firstLine) {
+   maxY = Math.max(maxY, top);
+ }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

              if (i === firstLine) {
                maxY = Math.max(maxY, top);
              }
🧰 Tools
🪛 Biome

[error] 456-456: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


458-458: 🛠️ Refactor suggestion

Avoid assignments within conditional expressions

Assigning minX = Math.min(minX, left) within a conditional expression can be confusing.

Refactor the code as follows:

- j === firstRow && (minX = Math.min(minX, left));
+ if (j === firstRow) {
+   minX = Math.min(minX, left);
+ }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

              if (j === firstRow) {
                minX = Math.min(minX, left);
              }
🧰 Tools
🪛 Biome

[error] 458-458: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


228-228: 🛠️ Refactor suggestion

Avoid assignments within conditional expressions

Using an assignment within a conditional expression can be confusing and reduce code readability. It's clearer to separate the condition and the assignment.

Refactor the code as follows:

- this._subFont && (this._subFont = null);
+ if (this._subFont !== null) {
+   this._subFont = null;
+ }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    if (this._subFont !== null) {
      this._subFont = null;
    }
🧰 Tools
🪛 Biome

[error] 228-228: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


445-446: 🛠️ Refactor suggestion

Avoid assignments within conditional expressions

Assigning firstRow = j within a conditional expression can be misleading. It's better to use an explicit if statement for clarity.

Refactor the code as follows:

- firstRow < 0 && (firstRow = j);
+ if (firstRow < 0) {
+   firstRow = j;
+ }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

              if (firstRow < 0) {
                firstRow = j;
              }
              const charRenderInfo = (charRenderInfos[renderElementCount++] = charRenderInfoPool.get());
🧰 Tools
🪛 Biome

[error] 445-445: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 446-446: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/ui/src/component/UICanvas.ts (7)

406-408: ⚠️ Potential issue

Add missing break statement to prevent fallthrough in switch case

The case for ComponentType.UIRenderer is missing a break statement, causing it to unintentionally fall through to the next case ComponentType.UIInteractive. This can lead to unexpected behavior.

Apply this diff to fix the issue:

            switch (componentType) {
              case ComponentType.UIRenderer:
                elements[depth] = component as unknown as IGraphics;
                ++depth;
+               break;
              case ComponentType.UIInteractive:
                if ((component as unknown as IGroupAble)._isGroupDirty) {
                  tempGroupAbleList[groupAbleCount++] = component as unknown as IGroupAble;
                }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        case ComponentType.UIRenderer:
          elements[depth] = component as unknown as IGraphics;
          ++depth;
          break;
🧰 Tools
🪛 Biome

[error] 406-408: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


222-222: 🛠️ Refactor suggestion

Avoid assignments within variable declarations

Assigning a value within a variable declaration can reduce readability and may introduce unintended side effects. It's better to separate the assignments.

Apply the following diff to refactor the code:

-        const renderElement = (this._renderElement = engine._renderElementPool.get());
+        this._renderElement = engine._renderElementPool.get();
+        const renderElement = this._renderElement;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        this._renderElement = engine._renderElementPool.get();
        const renderElement = this._renderElement;
🧰 Tools
🪛 Biome

[error] 222-222: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


587-588: ⚠️ Potential issue

Add missing break statement to prevent fallthrough in switch case

The case for CanvasRenderMode.ScreenSpaceOverlay is missing a break statement, causing unintended fallthrough to the next case. This can lead to unexpected behavior.

Apply this diff to fix the issue:

          switch (preRealMode) {
            case CanvasRenderMode.ScreenSpaceOverlay:
              this._removeCanvasListener();
+             break;
            case CanvasRenderMode.ScreenSpaceCamera:
            case CanvasRenderMode.WorldSpace:
              componentsManager.removeUICanvas(this, preRealMode === CanvasRenderMode.ScreenSpaceOverlay);
              break;

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Biome

[error] 587-588: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


437-437: 🛠️ Refactor suggestion

Avoid assignments within logical expressions

Assigning within a logical expression can reduce code clarity and introduce side effects. It's better to separate the assignment from the expression.

Apply this diff to refactor the code:

-          child.isActive && (depth = this._walk(child, elements, depth));
+          if (child.isActive) {
+            depth = this._walk(child, elements, depth);
+          }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

      if (child.isActive) {
            depth = this._walk(child, elements, depth);
          }
🧰 Tools
🪛 Biome

[error] 437-437: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


597-601: ⚠️ Potential issue

Add missing break statements to prevent fallthrough in switch cases

The case clauses for CanvasRenderMode.ScreenSpaceOverlay and CanvasRenderMode.ScreenSpaceCamera are missing break statements, leading to unintended fallthrough. This can cause the code under subsequent cases to execute unintentionally.

Apply this diff to fix the issue:

          switch (curRealMode) {
            case CanvasRenderMode.ScreenSpaceOverlay:
              this._addCanvasListener();
+             break;
            case CanvasRenderMode.ScreenSpaceCamera:
              this._adapterPoseInScreenSpace();
              this._adapterSizeInScreenSpace();
+             break;
            case CanvasRenderMode.WorldSpace:
              componentsManager.addUICanvas(this, curRealMode === CanvasRenderMode.ScreenSpaceOverlay);
              break;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        case CanvasRenderMode.ScreenSpaceOverlay:
          this._addCanvasListener();
          break;
        case CanvasRenderMode.ScreenSpaceCamera:
          this._adapterPoseInScreenSpace();
          this._adapterSizeInScreenSpace();
          break;
🧰 Tools
🪛 Biome

[error] 597-598: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 599-601: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


159-159: 🛠️ Refactor suggestion

Avoid assignments within logical expressions

Assigning values within logical expressions can be confusing and may lead to unintended side effects. It's clearer to separate the assignment from the expression.

Apply the following diff to refactor the code:

-          this._realRenderMode === CanvasRenderMode.ScreenSpaceOverlay &&
-            (this.scene._componentsManager._overlayCanvasesSortingFlag = true);
+          if (this._realRenderMode === CanvasRenderMode.ScreenSpaceOverlay) {
+            this.scene._componentsManager._overlayCanvasesSortingFlag = true;
+          }

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Biome

[error] 159-159: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


236-238: ⚠️ Potential issue

Wrap variable declarations in switch cases with braces

Variables declared inside a case clause are accessible in subsequent cases due to JavaScript's scoping rules, which can lead to unexpected behavior. To prevent this, wrap the case block in braces to create a new scope.

Apply this diff to fix the issue:

            switch (mode) {
              case CanvasRenderMode.ScreenSpaceOverlay:
+            {
                const { min, max } = renderer.bounds;
                if (min.x > width || max.x < 0 || min.y > height || max.y < 0) {
                  continue;
                }
+            }

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Biome

[error] 237-237: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

packages/design/src/ui/IUIElement.ts (1)

1-4: 🛠️ Refactor suggestion

Consider revising the API design for better encapsulation

The current design exposes implementation details through underscore-prefixed members in a public interface, which is unconventional. Consider separating the public and internal APIs.

Here's a suggested refactor:

-export interface IUIElement {
-  _onUIUpdateIndex: number;
-  _onUpdate(): void;
+/** Public interface for UI elements */
+export interface IUIElement {
+  /** Updates the UI element state */
+  update(): void;
+}
+
+/** Internal interface for UI system management */
+export interface IInternalUIElement extends IUIElement {
+  readonly updateIndex: number;
+}

This approach:

  1. Separates public and internal APIs
  2. Makes the update index readonly to prevent external modifications
  3. Provides a cleaner public interface for consumers
  4. Maintains type safety for internal system management
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

/** Public interface for UI elements */
export interface IUIElement {
  /** Updates the UI element state */
  update(): void;
}

/** Internal interface for UI system management */
export interface IInternalUIElement extends IUIElement {
  readonly updateIndex: number;
}
packages/design/src/ui/IUICanvas.ts (3)

1-7: 🛠️ Refactor suggestion

Improve type safety and interface design

The interface has several areas that could be improved:

  1. Replace any types with specific types to enhance type safety and IDE support
  2. Avoid mixing public and private members in interfaces
  3. Add JSDoc documentation

Consider refactoring the interface like this:

+ /**
+  * Represents a UI canvas that can be rendered in the scene.
+  */
 export interface IUICanvas {
-  entity: any;
-  sortOrder: number;
-  _canvasIndex: number;
-  _renderElement: any;
-  _prepareRender(renderContext: any): void;
+  /** The entity associated with this canvas */
+  entity: Entity;  // Replace with actual entity type
+  /** Determines the rendering order of the canvas */
+  sortOrder: number;
+  /** Prepares the canvas for rendering */
+  prepareRender(renderContext: RenderContext): void;  // Replace with actual context type
 }

+ /**
+  * Internal implementation details for UI canvas.
+  * Consider moving these to a separate implementation class.
+  */
+ interface IUICanvasImpl extends IUICanvas {
+  canvasIndex: number;
+  renderElement: RenderElement;  // Replace with actual element type
 }

Committable suggestion skipped: line range outside the PR's diff.


2-5: 💡 Codebase verification

Based on the search results, I can now generate the final response:

Use Entity and RenderElement types instead of any

The interface properties should be typed as follows:

  • entity should be typed as Entity since it's consistently used across the UI system
  • _renderElement should be typed as RenderElement as shown by its usage in UI canvas implementations

The types are available in the codebase and using them will provide better type safety and IDE support.

🔗 Analysis chain

Consider using strict typing for entity and renderElement

The use of any type reduces type safety. This could lead to runtime errors that TypeScript could otherwise catch during compilation.

Let's check how these types are used in the codebase:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for entity type definitions and usage
echo "Searching for Entity type definitions..."
ast-grep --pattern 'class $_ implements Entity'
ast-grep --pattern 'interface Entity'

echo "Searching for RenderElement type definitions..."
ast-grep --pattern 'class $_ implements RenderElement'
ast-grep --pattern 'interface RenderElement'

Length of output: 140


Script:

#!/bin/bash
# Let's try a broader search to understand the usage of these properties
echo "Searching for entity usage..."
rg "entity:" -A 2 -B 2

echo "\nSearching for _renderElement usage..."
rg "_renderElement:" -A 2 -B 2

echo "\nSearching for imports in the file..."
rg "import.*from" "packages/design/src/ui/IUICanvas.ts"

echo "\nChecking if this is an interface definition..."
rg "interface.*IUICanvas" "packages/design/src/ui/IUICanvas.ts"

Length of output: 36919


Script:

#!/bin/bash
# Let's search for the actual types used in implementations of IUICanvas
echo "Searching for UICanvas class implementations..."
rg "class.*implements.*IUICanvas"

echo "\nSearching for RenderElement type usage..."
rg "RenderElement" -A 2 -B 2

echo "\nSearching for Entity type usage..."
rg "Entity" "packages/design/src/ui/IUICanvas.ts"

Length of output: 33934


6-6: 💡 Codebase verification

Change any type to RenderContext from '@galacean/engine-core'

The renderContext parameter in _prepareRender should use the RenderContext type from the core package, as evidenced by:

  • The RenderContext class is defined in packages/core/src/RenderPipeline/RenderContext.ts
  • The method is used in UIUtils.ts with an instance of this RenderContext class
  • The interface is already importing other types from the core package
🔗 Analysis chain

Add type definition for renderContext parameter

The renderContext parameter should have a specific type rather than any to ensure type safety and provide better IDE support.

Let's check for existing render context types:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for RenderContext type definitions
echo "Searching for RenderContext type definitions..."
ast-grep --pattern 'interface RenderContext'
ast-grep --pattern 'class $_ implements RenderContext'

# Search for usage patterns
echo "Searching for renderContext parameter usage..."
rg -t ts "renderContext" -A 2 -B 2

Length of output: 208


Script:

#!/bin/bash
# Let's search more broadly for render context related types and usages
echo "Searching for render context related types..."
rg -t ts "type.*RenderContext|interface.*RenderContext|class.*RenderContext" -B 1 -A 3

echo "\nSearching for _prepareRender implementations..."
ast-grep --pattern 'class $_ {
  $$$
  _prepareRender($_) {
    $$$
  }
  $$$
}'

echo "\nSearching for files importing IUICanvas..."
rg -t ts "import.*IUICanvas"

Length of output: 1137


Script:

#!/bin/bash
# Let's examine the RenderContext class in detail
echo "Examining RenderContext class..."
cat packages/core/src/RenderPipeline/RenderContext.ts

# Let's also check how _prepareRender is used with RenderContext
echo "\nChecking _prepareRender usage with RenderContext..."
rg -t ts "_prepareRender.*renderContext" -B 2 -A 2

Length of output: 3941

packages/core/src/2d/text/ITextRenderer.ts (1)

7-7: 🛠️ Refactor suggestion

Reconsider the underscore prefix in method name

The method _getSubFont uses an underscore prefix, which typically indicates a private or internal member. However, in TypeScript interfaces, all members are inherently public. Consider removing the underscore prefix to better align with TypeScript conventions.

- _getSubFont(): SubFont;
+ getSubFont(): SubFont;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  getSubFont(): SubFont;
packages/ui/src/interface/IElement.ts (2)

4-6: 🛠️ Refactor suggestion

Add JSDoc documentation and consider architectural improvements.

  1. Add comprehensive JSDoc documentation to explain:

    • The purpose and responsibility of the IElement interface
    • The relationship between elements, entities, and canvas
    • Each property's purpose and usage
  2. Consider architectural improvements:

    • Could we reduce coupling with Entity by using a more abstract interface?
    • Should we add methods for lifecycle management and state updates?

Example documentation:

+/**
+ * Represents a UI element that can be rendered on a canvas.
+ * Provides core properties for managing the element's relationship
+ * with its underlying entity and canvas.
+ */
 export interface IElement {
+  /** The engine entity associated with this UI element */
   entity: Entity;
+  /** The canvas this element is rendered on */
   readonly canvas: UICanvas;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

/**
 * Represents a UI element that can be rendered on a canvas.
 * Provides core properties for managing the element's relationship
 * with its underlying entity and canvas.
 */
export interface IElement {
  /** The engine entity associated with this UI element */
  entity: Entity;
  /** The canvas this element is rendered on */
  readonly canvas: UICanvas;

8-12: 💡 Codebase verification

⚠️ Potential issue

Based on the results, I can now generate the final response:

Consider using existing event patterns instead of exposing internal implementation details

The codebase already has established patterns for event handling, including:

  1. Proper event type enums (e.g., PointerEventType, XRInputEventType)
  2. Type-safe event parameters
  3. Clean event registration/unregistration mechanisms

Suggested refactor:

export enum CanvasEventType {
  Update = 1,
  Resize = 2,
  // Add other relevant types
}

export interface IElement {
  readonly entity: Entity;
  readonly canvas: UICanvas;
  
  // Public event API
  addEventListener(type: CanvasEventType, listener: (entity: Entity) => void): void;
  removeEventListener(type: CanvasEventType, listener: (entity: Entity) => void): void;
  dispose(): void;
}

This aligns with:

  • Existing event patterns in the codebase
  • Proper encapsulation of implementation details
  • Type-safe event handling
  • Clear cleanup through dispose pattern
🔗 Analysis chain

Critical: Refactor internal implementation details and improve type safety.

Several concerns with the current implementation:

  1. Internal implementation details shouldn't be exposed in the interface:

    • _indexInCanvas, _isCanvasDirty, _canvasListeningEntities should be implementation details
    • Consider moving these to an abstract class or internal interface
  2. Type safety issues:

    • Avoid using any type
    • The flag parameter should use an enum or const union type
  3. Potential memory management issues:

    • Storing entity references in _canvasListeningEntities could lead to memory leaks
    • Need clear cleanup mechanism for entity listeners

Here's a suggested refactor:

/** Defines valid canvas event types */
export enum CanvasEventType {
  Update = 1,
  Resize = 2,
  // ... add other event types
}

/** Type for canvas event parameters */
export type CanvasEventParam = {
  type: CanvasEventType;
  data: unknown;
};

export interface IElement {
  entity: Entity;
  readonly canvas: UICanvas;

  // Public methods instead of exposed internals
  addEventListener(type: CanvasEventType, listener: (event: CanvasEventParam) => void): void;
  removeEventListener(type: CanvasEventType, listener: (event: CanvasEventParam) => void): void;
  dispose(): void;
}

Let's verify the usage of internal properties across the codebase:


The initial results show extensive usage of these internal properties across multiple components. Let's verify:

  1. How the flag numbers are defined/used
  2. Whether proper cleanup mechanisms exist
  3. The implementation of the unregister mechanism

This will help determine if the suggested refactor is necessary or if the current implementation already has safeguards in place.


The previous results don't show clear event type definitions or proper cleanup patterns. Let's verify:

  1. If there are existing event type enums/constants
  2. The implementation of the registration mechanism
  3. Any type definitions for events

This will help determine if we need to introduce new event types or if there's an existing pattern we should follow.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for direct access to internal properties
rg "_indexInCanvas|_isCanvasDirty|_canvasListeningEntities|_canvasListener" --type ts

# Look for potential memory leak patterns with entity references
ast-grep --pattern 'class $_ implements IElement {
  $$$
  _canvasListeningEntities: Entity[] = [];
  $$$
}'

Length of output: 4697


Script:

#!/bin/bash
# Check for event type usage patterns
rg "flag.*=.*\d+" --type ts -A 2

# Check for cleanup/disposal patterns
ast-grep --pattern 'dispose() {
  $$$
  _unRegisterListener($$$)
  $$$
}'

# Check Utils._unRegisterListener implementation
ast-grep --pattern '_unRegisterListener($$$) {
  $$$
}'

Length of output: 2009


Script:

#!/bin/bash
# Check for event flag definitions and constants
rg "enum.*Event|const.*Event" --type ts -A 5

# Check Utils class implementation for event handling
ast-grep --pattern 'class Utils {
  $$$
  _registerListener($$$) {
    $$$
  }
  $$$
}'

# Look for event type definitions
fd -e ts -x grep -l "type.*Event"

Length of output: 29878

packages/ui/src/interface/IGroupAble.ts (1)

6-12: 🛠️ Refactor suggestion

Consider encapsulating internal state and improving type safety.

The interface mixes public and private members, which could lead to maintenance issues:

  1. Properties prefixed with underscore suggest internal implementation details but are still publicly accessible
  2. Optional properties (_globalAlpha, _globalInteractive) lack documentation explaining when they might be undefined

Consider this alternative structure:

 export interface IGroupAble extends IElement {
   readonly group: UIGroup;
-  _indexInGroup: number;
-  _groupListeningEntities: Entity[];
-  _isGroupDirty: boolean;
-  _onUIUpdateIndex?: number;
-  _globalAlpha?: number;
-  _globalInteractive?: boolean;
+  /** @internal */
+  private readonly _state: {
+    indexInGroup: number;
+    groupListeningEntities: Entity[];
+    isGroupDirty: boolean;
+    onUIUpdateIndex?: number;
+  };
+  
+  /**
+   * Global alpha value for the group.
+   * @description Controls the opacity of all elements in the group.
+   */
+  globalAlpha: number;
+  
+  /**
+   * Global interactive state for the group.
+   * @description Determines if the group and its children can receive input events.
+   */
+  globalInteractive: boolean;

Committable suggestion skipped: line range outside the PR's diff.

packages/ui/src/component/interactive/transition/ScaleTransition.ts (1)

18-24: 🛠️ Refactor suggestion

Consider enhancing transition smoothness and adding validation.

A few suggestions for improvement:

  1. Consider using easing functions for smoother transitions
  2. Add validation for the weight parameter (should be between 0 and 1)
  3. Consider adding scale change event emission for animation tracking
   protected override _updateCurrentValue(srcValue: number, destValue: number, weight: number): void {
+    if (weight < 0 || weight > 1) {
+      throw new Error('Weight must be between 0 and 1');
+    }
-    this._currentValue = weight >= 1 ? destValue : (destValue - srcValue) * weight + srcValue;
+    // Apply easing function for smoother transition
+    const easedWeight = this._easeInOutQuad(weight);
+    this._currentValue = weight >= 1 ? destValue : (destValue - srcValue) * easedWeight + srcValue;
   }

+  private _easeInOutQuad(t: number): number {
+    return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
+  }

   protected override _applyValue(value: number): void {
     this._target.entity.transform.setScale(value, value, value);
+    this._target.entity.emit('scaleChange', value);
   }

Committable suggestion skipped: line range outside the PR's diff.

packages/core/src/2d/index.ts (1)

3-5: 💡 Codebase verification

Classes are not implementing the ISpriteAssembler interface

The sprite assembler classes (SimpleSpriteAssembler, SlicedSpriteAssembler, and TiledSpriteAssembler) are not implementing the ISpriteAssembler interface that exists in the codebase. This could lead to type safety issues and inconsistent implementations.

  • ./packages/core/src/2d/assembler/SimpleSpriteAssembler.ts: Class declared without interface implementation
  • ./packages/core/src/2d/assembler/SlicedSpriteAssembler.ts: Class declared without interface implementation
  • ./packages/core/src/2d/assembler/TiledSpriteAssembler.ts: Class declared without interface implementation
🔗 Analysis chain

Verify interface implementation in sprite assembler classes.

The new sprite assembler classes should implement the ISpriteAssembler interface.


Let me try a different approach to verify the interface implementation.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that sprite assembler classes implement ISpriteAssembler interface

# Check implementation in each assembler class
for file in SimpleSpriteAssembler SlicedSpriteAssembler TiledSpriteAssembler; do
  echo "Checking $file..."
  ast-grep --pattern 'class $file implements $interface {
    $$$
  }' ./packages/core/src/2d/assembler/$file.ts
done

Length of output: 851


Script:

#!/bin/bash
# Search for the class declarations and their inheritance/implementation
echo "=== SimpleSpriteAssembler ==="
rg "class SimpleSpriteAssembler" ./packages/core/src/2d/assembler -A 2

echo -e "\n=== SlicedSpriteAssembler ==="
rg "class SlicedSpriteAssembler" ./packages/core/src/2d/assembler -A 2

echo -e "\n=== TiledSpriteAssembler ==="
rg "class TiledSpriteAssembler" ./packages/core/src/2d/assembler -A 2

# Also search for ISpriteAssembler interface definition
echo -e "\n=== ISpriteAssembler interface ==="
rg "interface ISpriteAssembler" ./packages/core/src/2d -A 2

Length of output: 1861

packages/ui/src/component/interactive/transition/SpriteTransition.ts (1)

9-32: 🛠️ Refactor suggestion

Improve type safety and reduce code duplication in _destroy method.

Several concerns in the cleanup logic:

  1. Multiple @ts-ignore comments without explanation
  2. Repetitive cleanup pattern for each state
  3. No error handling for reference counting

Consider refactoring to:

- override _destroy(): void {
-   if (this._normal) {
-     // @ts-ignore
-     this._normal._addReferCount(-1);
-     this._normal = null;
-   }
-   if (this._hover) {
-     // @ts-ignore
-     this._hover._addReferCount(-1);
-     this._hover = null;
-   }
-   if (this._pressed) {
-     // @ts-ignore
-     this._pressed._addReferCount(-1);
-     this._pressed = null;
-   }
-   if (this._disabled) {
-     // @ts-ignore
-     this._disabled._addReferCount(-1);
-     this._disabled = null;
-   }
-   this._initialValue = this._currentValue = this._finalValue = null;
-   this._target = null;
+ private _cleanupSprite(sprite: Sprite | null): void {
+   if (sprite) {
+     try {
+       (sprite as any)._addReferCount(-1);
+     } catch (e) {
+       console.warn('Failed to cleanup sprite reference:', e);
+     }
+   }
+ }
+
+ override _destroy(): void {
+   const states = ['_normal', '_hover', '_pressed', '_disabled'];
+   states.forEach(state => {
+     this._cleanupSprite(this[state]);
+     this[state] = null;
+   });
+   
+   this._initialValue = this._currentValue = this._finalValue = null;
+   this._target = null;
}

Also, please add a comment explaining why the type assertion is necessary for _addReferCount.

Committable suggestion skipped: line range outside the PR's diff.

packages/ui/src/component/advanced/Button.ts (2)

32-35: 🛠️ Refactor suggestion

Improve cleanup implementation in onDestroy.

Similar to the removeClicked method, the cleanup could be more explicit and effective.

Consider this implementation:

 override onDestroy(): void {
   super.onDestroy();
-  this._listeners.findAndRemove((value) => (value.destroyed = true));
+  // Clear all listeners
+  this._listeners.length = 0;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  override onDestroy(): void {
    super.onDestroy();
    // Clear all listeners
    this._listeners.length = 0;
  }
🧰 Tools
🪛 Biome

[error] 34-34: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


11-21: 🛠️ Refactor suggestion

Improve listener management implementation.

The current implementation has two issues:

  1. Using assignment within the findAndRemove predicate reduces code clarity
  2. Marking listeners as destroyed without removing them could lead to memory leaks

Consider this cleaner implementation:

 removeClicked(listener: (event: PointerEventData) => void): void {
-  this._listeners.findAndRemove((value) => (value.fn === listener ? (value.destroyed = true) : false));
+  this._listeners.findAndRemove((value) => {
+    if (value.fn === listener) {
+      value.destroyed = true;
+      return true;  // Remove the listener instead of just marking it
+    }
+    return false;
+  });
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  addClicked(listener: (event: PointerEventData) => void): void {
    this._listeners.push({ fn: listener });
  }

  /**
   * Remove a listening function of click.
   * @param listener - The listening function
   */
  removeClicked(listener: (event: PointerEventData) => void): void {
    this._listeners.findAndRemove((value) => {
      if (value.fn === listener) {
        value.destroyed = true;
        return true;  // Remove the listener instead of just marking it
      }
      return false;
    });
  }
🧰 Tools
🪛 Biome

[error] 20-20: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/ui/UIUtils.ts (1)

15-15: 🛠️ Refactor suggestion

Add input validation for method parameters

The render method lacks validation for its input parameters. This could lead to runtime errors if invalid inputs are provided.

 static render(engine: Engine, uiCanvases: DisorderedArray<IUICanvas>): void {
+  if (!engine || !uiCanvases) {
+    throw new Error("Engine and uiCanvases parameters are required");
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  static render(engine: Engine, uiCanvases: DisorderedArray<IUICanvas>): void {
    if (!engine || !uiCanvases) {
      throw new Error("Engine and uiCanvases parameters are required");
    }
packages/ui/src/component/interactive/transition/ColorTransition.ts (2)

62-64: ⚠️ Potential issue

Add null check in _applyValue method.

The _applyValue method directly assigns to _target.color without checking if _target is defined.

   protected override _applyValue(value: Color): void {
-    this._target.color = value;
+    if (this._target) {
+      this._target.color = value;
+    }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  protected override _applyValue(value: Color): void {
    if (this._target) {
      this._target.color = value;
    }
  }

22-29: 💡 Codebase verification

⚠️ Potential issue

Remove @ts-ignore comments by properly typing _onValueChanged property

The Color class already has the _onValueChanged property properly defined as () => void internally. The type safety issues can be resolved by:

  1. Removing the @ts-ignore comments
  2. Ensuring the callback methods (_onNormalValueChanged, etc.) match the () => void type signature

Example fix:

this._normal._onValueChanged = this._onNormalValueChanged;
this._hover._onValueChanged = this._onHoverValueChanged;
this._pressed._onValueChanged = this._onPressedValueChanged;
this._disabled._onValueChanged = this._onDisabledValueChanged;
🔗 Analysis chain

Address type safety issues instead of using @ts-ignore.

Multiple @ts-ignore comments suggest underlying type safety issues. Consider addressing these properly by:

  1. Adding proper type definitions for the _onValueChanged property
  2. Extending the Color class if needed

Let's verify if the Color class can be extended or modified:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for Color class definition to understand its structure
ast-grep --pattern 'class Color {
  $$$
}'

Length of output: 44


Script:

#!/bin/bash
# Let's try to find the Color class definition with ripgrep
rg "class Color" -A 10

# Also search for any type definitions related to Color
rg "type.*Color" --type=typescript

# Search for Color-related files
fd -e ts -e tsx "Color"

# Search for _onValueChanged usage
rg "_onValueChanged" -A 2

Length of output: 38995


Script:

#!/bin/bash
# Let's examine the Color class implementation to understand its type structure
cat packages/math/src/Color.ts

Length of output: 9743

packages/core/src/ComponentsDependencies.ts (2)

14-14: 🛠️ Refactor suggestion

Fix type and consider adding documentation.

  1. Use boolean instead of Boolean for the map value type
  2. Consider adding JSDoc to document the purpose of this map
  3. Consider making the value type readonly since it's internal state
-  static _inheritedMap = new Map<ComponentConstructor, Boolean>();
+  /** Map tracking which components should be inherited by child entities */
+  private static _inheritedMap = new Map<ComponentConstructor, boolean>();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  /** Map tracking which components should be inherited by child entities */
  private static _inheritedMap = new Map<ComponentConstructor, boolean>();
🧰 Tools
🪛 Biome

[error] 14-14: Don't use 'Boolean' as a type.

Use lowercase primitives for consistency.
Safe fix: Use 'boolean' instead

(lint/complexity/noBannedTypes)


97-101: 🛠️ Refactor suggestion

Enhance type safety and add validation.

The decorator should validate its usage and prevent potential memory leaks.

 export function markAsInherited() {
   return function <T extends ComponentConstructor>(target: T): void {
+    if (!(target.prototype instanceof Component)) {
+      throw new Error("markAsInherited can only be used on Component classes");
+    }
+    if (ComponentsDependencies._inheritedMap.has(target)) {
+      console.warn(`Component ${target.name} is already marked as inherited`);
+      return;
+    }
     ComponentsDependencies._inheritedMap.set(target, true);
   };
 }

Additionally, consider adding a cleanup mechanism to prevent memory leaks when components are removed or unregistered.

Committable suggestion skipped: line range outside the PR's diff.

packages/core/src/2d/assembler/SlicedSpriteAssembler.ts (2)

65-69: 🛠️ Refactor suggestion

Improve code readability by separating combined assignments

The combined assignment statements make the code harder to read and maintain. This was previously discussed but still needs to be addressed.

Apply this diff to improve readability:

- (row[0] = expectWidth * left * widthScale), (row[1] = row[2] = fixedLeft * widthScale);
- row[3] = width - expectWidth * (1 - right) * widthScale;
+ row[0] = expectWidth * left * widthScale;
+ row[1] = fixedLeft * widthScale;
+ row[2] = fixedLeft * widthScale;
+ row[3] = width - expectWidth * (1 - right) * widthScale;

- (column[0] = expectHeight * bottom * heightScale), (column[1] = column[2] = fixedBottom * heightScale);
- column[3] = height - expectHeight * (1 - top) * heightScale;
+ column[0] = expectHeight * bottom * heightScale;
+ column[1] = fixedBottom * heightScale;
+ column[2] = fixedBottom * heightScale;
+ column[3] = height - expectHeight * (1 - top) * heightScale;

Also applies to: 74-78


169-173: 🛠️ Refactor suggestion

Maintain consistent code style in getUVByLocalPosition

The same combined assignment pattern is used here. For consistency with the earlier suggestions, these should also be separated.

Apply this diff:

- (row[0] = expectWidth * left * widthScale), (row[1] = row[2] = fixedLeft * widthScale);
- row[3] = width - expectWidth * (1 - right) * widthScale;
+ row[0] = expectWidth * left * widthScale;
+ row[1] = fixedLeft * widthScale;
+ row[2] = fixedLeft * widthScale;
+ row[3] = width - expectWidth * (1 - right) * widthScale;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

      row[0] = expectWidth * left * widthScale;
      row[1] = fixedLeft * widthScale;
      row[2] = fixedLeft * widthScale;
      row[3] = width - expectWidth * (1 - right) * widthScale;
    } else {
      (row[0] = expectWidth * left), (row[1] = fixedLeft), (row[2] = width - fixedRight);
      row[3] = width - expectWidth * (1 - right);
packages/core/src/input/pointer/PointerManager.ts (2)

151-157: ⚠️ Potential issue

Potential race condition in drag event processing

While the comment indicates that drag must be processed first, there's no synchronization mechanism to prevent race conditions when multiple emitters process events concurrently.

Consider adding a synchronization mechanism or using a queue to ensure proper event ordering:

- emitters.forEach((emitter) => {
-   emitter.processDrag(pointer);
- });
+ // Process drag events sequentially
+ for (const emitter of emitters) {
+   await emitter.processDrag(pointer);
+ }

Committable suggestion skipped: line range outside the PR's diff.


231-262: 🛠️ Refactor suggestion

Add error boundaries for event processing

The event handling logic should include error handling to prevent cascading failures.

Consider wrapping the event processing in try-catch blocks:

 switch (event.type) {
   case "pointerdown": {
+    try {
       const button = event.button;
       _downList.add(button);
       _downMap[button] = frameCount;
       pointer._downList.add(button);
       pointer._downMap[button] = frameCount;
       pointer._frameEvents |= PointerEventType.Down;
       pointer.phase = PointerPhase.Down;
+    } catch (error) {
+      console.error('Error processing pointer down event:', error);
+      pointer.phase = PointerPhase.Leave;  // Fail safe
+    }
     break;
   }

Committable suggestion skipped: line range outside the PR's diff.

packages/core/src/ComponentsManager.ts (2)

185-188: ⚠️ Potential issue

Fix incorrect array reference in addOnUpdateUIElement

There's a critical bug where the method uses this._onUpdateRenderers.length instead of this._onUpdateUIElements.length for the index.

Apply this diff to fix the bug:

-    element._onUIUpdateIndex = this._onUpdateRenderers.length;
+    element._onUIUpdateIndex = this._onUpdateUIElements.length;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  addOnUpdateUIElement(element: IUIElement): void {
    element._onUIUpdateIndex = this._onUpdateUIElements.length;
    this._onUpdateUIElements.add(element);
  }

272-276: 🛠️ Refactor suggestion

Align UI update pattern with other component updates

The callUIOnUpdate method doesn't follow the consistent pattern of managing indices during iteration. This could lead to stale indices.

Apply this diff to align with other update methods:

   callUIOnUpdate(deltaTime: number): void {
-    this._onUpdateUIElements.forEach((element: IUIElement) => {
-      element._onUpdate();
-    });
+    this._onUpdateUIElements.forEach(
+      (element: IUIElement) => {
+        element._onUpdate();
+      },
+      (element: IUIElement, index: number) => {
+        element._onUIUpdateIndex = index;
+      }
+    );
   }

Committable suggestion skipped: line range outside the PR's diff.

packages/core/src/2d/text/TextUtils.ts (3)

98-103: ⚠️ Potential issue

Add parameter validation

The method should validate its numeric parameters to prevent potential rendering issues.

Add this validation at the start of the method:

 static measureTextWithWrap(
   renderer: ITextRenderer,
   rendererWidth: number,
   rendererHeight: number,
   lineSpacing: number
 ): TextMetrics {
+  if (rendererWidth <= 0 || rendererHeight <= 0 || lineSpacing < 0) {
+    throw new Error('Invalid dimensions: width and height must be positive, lineSpacing must be non-negative');
+  }
   const subFont = renderer._getSubFont();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  static measureTextWithWrap(
    renderer: ITextRenderer,
    rendererWidth: number,
    rendererHeight: number,
    lineSpacing: number
  ): TextMetrics {
    if (rendererWidth <= 0 || rendererHeight <= 0 || lineSpacing < 0) {
      throw new Error('Invalid dimensions: width and height must be positive, lineSpacing must be non-negative');
    }
    const subFont = renderer._getSubFont();

98-103: 💡 Codebase verification

Tests need to be updated with new method parameters

The test file is using outdated method signatures for both measureTextWithWrap and measureTextWithoutWrap. All production code has been properly updated, but the test file needs to be fixed. Here are the issues:

  • In measureTextWithWrap test cases, the method is called without required parameters: rendererWidth, rendererHeight, and lineSpacing
  • In measureTextWithoutWrap test cases, the method is called without required parameters: rendererHeight and lineSpacing

The test renderer objects already have these values set up:

  • width and height properties are set before each test
  • lineSpacing can be derived from the expected lineHeight assertions (27)
🔗 Analysis chain

Verify all callers are updated with new parameters

The signature changes for both measurement methods require verification that all calling code has been updated accordingly.

Also applies to: 275-275


Let me analyze the search results:

For measureTextWithWrap, there are calls in:

  1. packages/ui/src/component/advanced/Label.ts - Correctly provides all parameters: this, rendererWidth, rendererHeight, this._lineSpacing
  2. packages/core/src/2d/text/TextRenderer.ts - Correctly provides all parameters (using _pixelsPerUnit scaling)
  3. tests/src/core/2d/text/TextUtils.test.ts - Test calls appear to be using old signature

For measureTextWithoutWrap, there are calls in:

  1. packages/ui/src/component/advanced/Label.ts - Correctly provides all parameters: this, rendererHeight, this._lineSpacing
  2. packages/core/src/2d/text/TextRenderer.ts - Correctly provides all parameters (using _pixelsPerUnit scaling)
  3. tests/src/core/2d/text/TextUtils.test.ts - Test calls appear to be using old signature

Let me verify the test file to see if it's outdated:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for all calls to these methods
echo "Searching for measureTextWithWrap calls:"
rg -A 2 "measureTextWithWrap\(" --type ts

echo -e "\nSearching for measureTextWithoutWrap calls:"
rg -A 2 "measureTextWithoutWrap\(" --type ts

Length of output: 7755


Script:

#!/bin/bash
# Check the test file content around the method calls
cat tests/src/core/2d/text/TextUtils.test.ts

Length of output: 14944


275-275: ⚠️ Potential issue

Add parameter validation

Similar to measureTextWithWrap, this method should validate its numeric parameters.

Add this validation at the start of the method:

 static measureTextWithoutWrap(
   renderer: ITextRenderer,
   rendererHeight: number,
   lineSpacing: number
 ): TextMetrics {
+  if (rendererHeight <= 0 || lineSpacing < 0) {
+    throw new Error('Invalid dimensions: height must be positive, lineSpacing must be non-negative');
+  }
   const subFont = renderer._getSubFont();

Committable suggestion skipped: line range outside the PR's diff.

packages/core/src/2d/assembler/TiledSpriteAssembler.ts (2)

81-86: ⚠️ Potential issue

Replace global isNaN with Number.isNaN

Using the global isNaN is unsafe as it performs type coercion. Use Number.isNaN instead for more predictable behavior.

-      if (isNaN(cUV.get(doubleJ)) || isNaN(cUV.get(doubleJ + 1))) {
+      if (Number.isNaN(cUV.get(doubleJ)) || Number.isNaN(cUV.get(doubleJ + 1))) {
         continue;
       }
       for (let i = 0; i < rowLength; i++) {
         const doubleI = i << 1;
-        if (isNaN(rUV.get(doubleI)) || isNaN(rUV.get(doubleI + 1))) {
+        if (Number.isNaN(rUV.get(doubleI)) || Number.isNaN(rUV.get(doubleI + 1))) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

      if (Number.isNaN(cUV.get(doubleJ)) || Number.isNaN(cUV.get(doubleJ + 1))) {
        continue;
      }
      for (let i = 0; i < rowLength; i++) {
        const doubleI = i << 1;
        if (Number.isNaN(rUV.get(doubleI)) || Number.isNaN(rUV.get(doubleI + 1))) {
🧰 Tools
🪛 Biome

[error] 81-81: isNaN is unsafe. It attempts a type coercion. Use Number.isNaN instead.

See the MDN documentation for more details.
Unsafe fix: Use Number.isNaN instead.

(lint/suspicious/noGlobalIsNan)


[error] 81-81: isNaN is unsafe. It attempts a type coercion. Use Number.isNaN instead.

See the MDN documentation for more details.
Unsafe fix: Use Number.isNaN instead.

(lint/suspicious/noGlobalIsNan)


[error] 86-86: isNaN is unsafe. It attempts a type coercion. Use Number.isNaN instead.

See the MDN documentation for more details.
Unsafe fix: Use Number.isNaN instead.

(lint/suspicious/noGlobalIsNan)


[error] 86-86: isNaN is unsafe. It attempts a type coercion. Use Number.isNaN instead.

See the MDN documentation for more details.
Unsafe fix: Use Number.isNaN instead.

(lint/suspicious/noGlobalIsNan)


422-444: ⚠️ Potential issue

Add block scoping to switch cases

Variables declared in switch cases can leak into other cases. Wrap the declarations in blocks to prevent this.

     switch (rType) {
       case TiledType.Compressed: {
-        const scale = width / fixedLR;
-        rPos.add(expectWidth * left * scale), rPos.add(fixedL * scale);
-        rPos.add(width - expectWidth * (1 - right) * scale);
-        rUV.add(spriteUV0.x), rUV.add(spriteUV1.x), rUV.add(spriteUV2.x), rUV.add(spriteUV3.x);
+        {
+          const scale = width / fixedLR;
+          rPos.add(expectWidth * left * scale), rPos.add(fixedL * scale);
+          rPos.add(width - expectWidth * (1 - right) * scale);
+          rUV.add(spriteUV0.x), rUV.add(spriteUV1.x), rUV.add(spriteUV2.x), rUV.add(spriteUV3.x);
+        }
         break;
       }
       case TiledType.WithTiled: {
-        const uv1 = spriteUV1.x;
-        const uv2 = spriteUV2.x;
-        const repeatWidth = (width - fixedLR) / rTiledCount;
+        {
+          const uv1 = spriteUV1.x;
+          const uv2 = spriteUV2.x;
+          const repeatWidth = (width - fixedLR) / rTiledCount;

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Biome

[error] 422-422: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 434-434: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 435-435: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 436-436: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

packages/core/src/Entity.ts (1)

613-622: 🛠️ Refactor suggestion

Fix assignment in expression

The modification listener management is good, but the assignment in expression should be split for better readability.

-    (this._updateFlagManager ||= new UpdateFlagManager()).addListener(onChange);
+    if (!this._updateFlagManager) {
+      this._updateFlagManager = new UpdateFlagManager();
+    }
+    this._updateFlagManager.addListener(onChange);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  _registerModifyListener(onChange: (flag: EntityModifyFlags) => void): void {
    if (!this._updateFlagManager) {
      this._updateFlagManager = new UpdateFlagManager();
    }
    this._updateFlagManager.addListener(onChange);
  }

  /**
   * @internal
   */
  _unRegisterModifyListener(onChange: (flag: EntityModifyFlags) => void): void {
    this._updateFlagManager?.removeListener(onChange);
  }
🧰 Tools
🪛 Biome

[error] 614-614: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments.

🛑 Comments failed to post (76)
packages/ui/src/component/UITransform.ts (1)

32-41: ⚠️ Potential issue

Avoid using @ts-ignore to suppress TypeScript errors

The use of @ts-ignore comments in the constructor to access private or protected properties can lead to maintenance issues and hides potential type safety problems. Specifically, accessing:

  • entity._transform.destroy();
  • Setting entity._transform = this;
  • Assigning to entity._inverseWorldMatFlag
  • Binding _onValueChanged callbacks for _size and _pivot

This practice violates encapsulation principles and may cause unexpected behavior if internal implementations of these classes change.

Consider the following actions:

  • Expose necessary functionality through public APIs: Modify the Entity, Transform, and Vector2 classes to provide public methods or properties that allow the required interactions.
  • Refactor the design: Reevaluate the class interactions to ensure that they adhere to object-oriented design principles without needing to access internal properties.

This approach maintains type safety and improves code maintainability.

packages/ui/src/component/interactive/transition/Transition.ts (4)

25-67: 🛠️ Refactor suggestion

Avoid comparing object references in setters; use appropriate equality checks instead

In the setters for normal, pressed, hover, and disabled (lines 25-67), comparing objects using !== may not accurately detect changes for object types like Sprite or Color, as it checks reference equality rather than value equality. This could lead to missed updates when the object's properties change but the reference remains the same. Consider implementing proper equality checks to ensure changes are detected correctly.

You can modify the comparison to use a deep equality check or implement an equality method for TransitionValueType. For example:

-if (preNormal !== value) {
+if (!this._areValuesEqual(preNormal, value)) {

And add a helper method _areValuesEqual:

private _areValuesEqual(a: T, b: T): boolean {
  if (typeof a === 'number' && typeof b === 'number') {
    return a === b;
  } else if (a instanceof Color && b instanceof Color) {
    return a.equals(b);
  } else if (a instanceof Sprite && b instanceof Sprite) {
    return a === b; // Update this logic if necessary to compare Sprites
  }
  return false;
}

151-162: ⚠️ Potential issue

Add a default case in '_getValueByState' to prevent returning undefined

In the _getValueByState method (lines 151-162), there is no default case in the switch statement. If an unexpected InteractiveState value is passed, the method may return undefined, leading to potential runtime errors when the value is used elsewhere. To enhance robustness, add a default case to handle unexpected states.

Apply the following modification:

private _getValueByState(state: InteractiveState): T {
  switch (state) {
    case InteractiveState.Normal:
      return this.normal;
    case InteractiveState.Pressed:
      return this.pressed;
    case InteractiveState.Hover:
      return this.hover;
    case InteractiveState.Disable:
      return this.disabled;
+   default:
+     return this.normal; // Or handle appropriately based on your application's logic
  }
}

Alternatively, you could throw an error for unrecognized states to catch issues during development:

default:
  throw new Error(`Unhandled InteractiveState: ${state}`);

134-143: 🛠️ Refactor suggestion

Avoid using '// @ts-ignore'; properly handle TypeScript type checks

In the _onStateValueDirty method (lines 134-143), suppressing TypeScript errors with // @ts-ignore is discouraged, as it can conceal underlying issues. Instead, use type assertions or type guards to handle cases where preValue and curValue might be instances of ReferResource.

Modify the code to include type checks without suppressing TypeScript errors:

-// @ts-ignore
-preValue instanceof ReferResource && preValue._addReferCount(-1);
-// @ts-ignore
-curValue instanceof ReferResource && curValue._addReferCount(1);
+if (preValue instanceof ReferResource) {
+  preValue._addReferCount(-1);
+}
+if (curValue instanceof ReferResource) {
+  curValue._addReferCount(1);
+}

Ensure that TransitionValueType is correctly defined to include types that may extend ReferResource, or adjust the type casting as needed.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  protected _onStateValueDirty(state: InteractiveState, preValue: T, curValue: T): void {
    if (preValue instanceof ReferResource) {
      preValue._addReferCount(-1);
    }
    if (curValue instanceof ReferResource) {
      curValue._addReferCount(1);
    }
    if (this._finalState === state) {
      this._finalValue = curValue;
      this._updateValue();
    }
  }

85-94: ⚠️ Potential issue

Fix potential division by zero in duration setter

In the set duration(value: number) method (lines 85-94), if preDuration is zero and _countDown > 0, the calculation this._countDown / preDuration will result in a division by zero error. This can cause runtime exceptions and unexpected behavior. Ensure that preDuration is not zero before performing the division to prevent this issue.

Apply the following fix:

const preDuration = this._duration;
if (preDuration !== value) {
  this._duration = value;
  if (this._countDown > 0) {
+   if (preDuration !== 0) {
      this._countDown = value * (1 - this._countDown / preDuration);
+   } else {
+     this._countDown = value;
+   }
    this._updateValue();
  }
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    if (value < 0) value = 0;
    const preDuration = this._duration;
    if (preDuration !== value) {
      this._duration = value;
      if (this._countDown > 0) {
        if (preDuration !== 0) {
          this._countDown = value * (1 - this._countDown / preDuration);
        } else {
          this._countDown = value;
        }
        this._updateValue();
      }
    }
  }
packages/ui/src/Utils.ts (5)

133-133: ⚠️ Potential issue

Avoid assignments within expressions for better clarity

For improved readability, refactor the assignment to be explicit.

Apply this diff:

-            replaced && (replaced._indexInCanvas = element._indexInCanvas);
+            if (replaced) {
+              replaced._indexInCanvas = element._indexInCanvas;
+            }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

      if (replaced) {
        replaced._indexInCanvas = element._indexInCanvas;
      }
🧰 Tools
🪛 Biome

[error] 133-133: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


100-100: ⚠️ Potential issue

Avoid assignments within expressions for better clarity

Using assignments within expressions can reduce code readability. Refactor the assignment to make it explicit.

Apply this diff:

-            replaced && (replaced._indexInGroup = element._indexInGroup);
+            if (replaced) {
+              replaced._indexInGroup = element._indexInGroup;
+            }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        if (replaced) {
          replaced._indexInGroup = element._indexInGroup;
        }
🧰 Tools
🪛 Biome

[error] 100-100: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


122-122: ⚠️ Potential issue

Avoid assignments within expressions for better clarity

Assignments within expressions can be confusing. It's better to make the assignment explicit.

Apply this diff:

-            replaced && (replaced._indexInGroup = element._indexInGroup);
+            if (replaced) {
+              replaced._indexInGroup = element._indexInGroup;
+            }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

      if (replaced) {
        replaced._indexInGroup = element._indexInGroup;
      }
🧰 Tools
🪛 Biome

[error] 122-122: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


135-135: ⚠️ Potential issue

Avoid assignments within expressions for better clarity

Making assignments explicit enhances code clarity.

Apply this diff:

-            isGraphics && (preCanvas._hierarchyDirty = true);
+            if (isGraphics) {
+              preCanvas._hierarchyDirty = true;
+            }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

            if (isGraphics) {
              preCanvas._hierarchyDirty = true;
            }
🧰 Tools
🪛 Biome

[error] 135-135: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


79-79: ⚠️ Potential issue

Avoid assignments within expressions for better clarity

Using assignments within expressions can make the code harder to read and may lead to confusion. It's recommended to refactor the assignment to make it more explicit.

Apply this diff to improve clarity:

-            replaced && (replaced._indexInCanvas = element._indexInCanvas);
+            if (replaced) {
+              replaced._indexInCanvas = element._indexInCanvas;
+            }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        if (replaced) {
          replaced._indexInCanvas = element._indexInCanvas;
        }
🧰 Tools
🪛 Biome

[error] 79-79: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/input/pointer/emitter/PhysicsPointerEventEmitter.ts (1)

61-61: 🛠️ Refactor suggestion

Avoid assignments within expressions to improve readability

Assigning variables within an expression can reduce code readability and may introduce unintended side effects. It's better to separate assignments into individual statements.

Apply this diff to improve clarity:

-    const entity = (this._pressedEntity = this._draggedEntity = this._enteredEntity);
+    this._pressedEntity = this._draggedEntity = this._enteredEntity;
+    const entity = this._pressedEntity;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    this._pressedEntity = this._draggedEntity = this._enteredEntity;
    const entity = this._pressedEntity;
🧰 Tools
🪛 Biome

[error] 61-61: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 61-61: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/ui/src/component/UIGroup.ts (4)

90-94: ⚠️ Potential issue

Avoid using @ts-ignore; ensure TypeScript compatibility instead.

Using @ts-ignore suppresses TypeScript errors, potentially hiding real issues. The override modifier and direct assignment to _componentType might not be recognized in your current TypeScript configuration. Ensure that your project uses a compatible TypeScript version (4.3 or higher) and adjust access modifiers or class structures to avoid the need for @ts-ignore.


148-149: ⚠️ Potential issue

Remove @ts-ignore and verify method overriding correctly.

The use of @ts-ignore before the override keyword suppresses potential TypeScript errors. Ensure that the base class Component has the method _onEnableInScene and that you're correctly overriding it. Updating your TypeScript version or compiler options might resolve this without ignoring errors.


184-190: ⚠️ Potential issue

Use bitwise operations when checking flags in _canvasListener.

Similar to the previous issue, the flag check should use bitwise operations to handle multiple flags correctly.

Apply this diff:

- if (flag === EntityModifyFlags.Parent) {
+ if ((flag & EntityModifyFlags.Parent) !== 0) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  _canvasListener(flag: number): void {
    if (this._isCanvasDirty) return;
    if ((flag & EntityModifyFlags.Parent) !== 0) {
      Utils._onCanvasDirty(this, this._canvas);
      Utils._onGroupDirty(this, this._group);
    }
  }

174-178: ⚠️ Potential issue

Use bitwise operations when checking flags in _groupListener.

The current flag check uses strict equality, which may fail if multiple flags are set simultaneously. Modify the condition to use bitwise operations to correctly detect flag combinations.

Apply this diff to fix the issue:

- if (flag === EntityModifyFlags.Parent || flag === EntityUIModifyFlags.UIGroupEnableInScene) {
+ if ((flag & (EntityModifyFlags.Parent | EntityUIModifyFlags.UIGroupEnableInScene)) !== 0) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    if (this._isGroupDirty) return;
    if ((flag & (EntityModifyFlags.Parent | EntityUIModifyFlags.UIGroupEnableInScene)) !== 0) {
      Utils._onGroupDirty(this, this._group);
    }
  }
packages/ui/src/component/interactive/UIInteractive.ts (4)

151-152: ⚠️ Potential issue

Avoid accessing private members of other classes

Accessing _componentsManager from this.scene in lines 151 and 162 is likely accessing a private member of the Scene class. Accessing private properties breaks encapsulation and can lead to maintenance issues.

Consider using public methods or properties provided by Scene to register and unregister UI elements. If such methods do not exist, it may be necessary to update the Scene class to expose the required functionality.

Also applies to: 162-163


176-183: 🛠️ Refactor suggestion

Ensure _onUpdate properly updates interaction state

Currently, _onUpdate only calls _updateGlobalInteractive(). To ensure that interaction states are consistently updated, consider also calling _updateState(false) within this method.

Apply this change:

  _onUpdate(): void {
    this._updateGlobalInteractive();
+   this._updateState(false);
  }

This guarantees that the state reflects any changes in interactivity each frame.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  /**
   * @internal
   */
  _onUpdate(): void {
    this._updateGlobalInteractive();
    this._updateState(false);
  }

  /**

146-149: ⚠️ Potential issue

Address the use of multiple @ts-ignore comments

The repeated use of // @ts-ignore suggests that TypeScript errors are being suppressed. This can hide potential issues and reduce type safety. Investigate and resolve the underlying TypeScript errors instead of suppressing them.

For example, if the _onEnableInScene method is not defined in the parent class or has a different access modifier, consider updating the method access or properly extending the class.

- // @ts-ignore
  override _onEnableInScene(): void {
-   // @ts-ignore
    super._onEnableInScene();
    // ...
  }

Ensure that _onEnableInScene is correctly declared in the parent class Script and that it can be overridden.

Committable suggestion skipped: line range outside the PR's diff.


104-109: 🛠️ Refactor suggestion

Refine generic type constraints in addTransition method

In the addTransition method, the generic type T is constrained as new () => Transition, but this may not correctly capture subclasses of Transition with their own constructor signatures.

Modify the generic constraint to accept any constructor of a Transition subclass:

-addTransition<T extends new () => Transition>(type: T): InstanceType<T> {
+addTransition<T extends Transition>(type: { new (...args: any[]): T }): T {
  const transition = new type();
  // ...
}

Similarly, update the return type to T for clarity.

Committable suggestion skipped: line range outside the PR's diff.

packages/ui/src/input/UIPointerEventEmitter.ts (8)

103-103: ⚠️ Potential issue

Avoid assignment within an expression for clarity

At line 103, there is an assignment within an expression:

const element = (this._pressedElement = this._draggedElement = this._enteredElement);

This can make the code harder to read and understand. It's better to separate the assignments for improved readability.

Apply this diff to refactor the code:

- const element = (this._pressedElement = this._draggedElement = this._enteredElement);
+ this._pressedElement = this._draggedElement = this._enteredElement;
+ const element = this._pressedElement;
🧰 Tools
🪛 Biome

[error] 103-103: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 103-103: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


222-222: ⚠️ Potential issue

Avoid assignment within an expression for clarity

At line 222, there's an assignment within an expression:

let entity = (path[0] = element.entity);

Assigning within an expression can reduce code readability. Separating the assignments makes the code clearer.

Apply this diff to refactor the code:

- let entity = (path[0] = element.entity);
+ path[0] = element.entity;
+ let entity = path[0];
🧰 Tools
🪛 Biome

[error] 222-222: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


35-35: 🛠️ Refactor suggestion

Accessing private properties using @ts-ignore is discouraged

At line 35:

// @ts-ignore
const componentsManager = scene._componentsManager;

Using @ts-ignore to access private members bypasses TypeScript's type checking, which can lead to maintenance issues and potential runtime errors.

Consider exposing componentsManager through a public getter or method in the Scene class. This way, you can access it without ignoring TypeScript's checks.


205-212: 🛠️ Refactor suggestion

Avoid using @ts-ignore to access private members

At lines 205-212:

// @ts-ignore
entity._scripts.forEach(
  (script: Script) => {
    script[methods]?.(eventData);
  },
  (script: Script, index: number) => {
    // @ts-ignore
    script._entityScriptsIndex = index;
  }
);

Using @ts-ignore to access _scripts and _entityScriptsIndex can lead to fragile code. It bypasses the safety offered by TypeScript's access modifiers.

Consider adding public methods or accessors to interact with the scripts. For example, the Entity class could provide a method to get its scripts, and Script could manage its index internally.


225-229: 🛠️ Refactor suggestion

Ensure proper access to component properties

At lines 225-229, you are accessing _componentType and _isRootCanvas:

// @ts-ignore
if (element._componentType === ComponentType.UICanvas && element._isRootCanvas) {
  path.length = 1;
  return path;
} else {

Accessing private or protected properties with @ts-ignore should be avoided as it circumvents TypeScript's type safety.

Propose exposing necessary properties or methods in the Component or UICanvas classes to access this information without using @ts-ignore.


217-235: ⚠️ Potential issue

Potential infinite loop in _composedPath method

In the _composedPath method, you have a loop that depends on entity.parent:

for (; i < UIPointerEventEmitter._MAX_PATH_DEPTH && !!entity && entity !== rootEntity; i++) {
  entity = path[i] = entity.parent;
}

There is a risk of entering an infinite loop if there is a cycle in the entity's parent hierarchy or if rootEntity is not correctly identified.

Consider adding safeguards to detect cycles or ensure that rootEntity is correctly set.

🧰 Tools
🪛 Biome

[error] 222-222: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


89-89: 💡 Codebase verification

Return statement is inconsistent with void return type

The processRaycast method is declared with a void return type but includes a return null statement. This is unnecessary since:

  1. The method already has multiple return statements without values
  2. Void functions should not return values in TypeScript

Remove the return null statement at line 89 as it's both unnecessary and inconsistent with the method's signature.

🔗 Analysis chain

Verify: Consistency of the return type with method signature

As per the static analysis hint, the function processRaycast is expected to return void, but it includes a return null; statement at line 89.

Run the following script to check for any other instances where void functions return a value:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find methods declared as void that return a value
ast-grep 'function $_(...) : void { $$$; return $_; $$$ }' --lang typescript

Length of output: 223


Script:

#!/bin/bash
# Let's first check the method signature and implementation
rg -U "processRaycast.*void.*\{[\s\S]*?return null" packages/ui/src/input/UIPointerEventEmitter.ts

# Also check the entire file content for better context
cat packages/ui/src/input/UIPointerEventEmitter.ts

Length of output: 10852

🧰 Tools
🪛 Biome

[error] 89-89: The function should not return a value because its return type is void.

The function is here:

'void' signals the absence of value. The returned value is likely to be ignored by the caller.

(lint/correctness/noVoidTypeReturn)


26-89: ⚠️ Potential issue

Remove the return statement in a void function

The processRaycast method is declared with a return type of void, but it includes a return null; statement at line 89. Returning a value from a void function can cause confusion and may lead to unexpected behavior.

Apply this diff to correct the issue:

89 -        return null;
+        return;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  override processRaycast(scenes: readonly Scene[], pointer: Pointer): void {
    const { _tempRay: ray } = PointerEventEmitter;
    const hitResult = this._hitResult;
    const { position } = pointer;
    const { x, y } = position;
    for (let i = scenes.length - 1; i >= 0; i--) {
      const scene = scenes[i];
      if (!scene.isActive || scene.destroyed) continue;
      // @ts-ignore
      const componentsManager = scene._componentsManager;
      /** Overlay Canvas */
      let canvasElements = componentsManager._overlayCanvases;
      ray.origin.set(position.x, position.y, 1);
      ray.direction.set(0, 0, -1);
      for (let j = canvasElements.length - 1; j >= 0; j--) {
        if (canvasElements.get(j).raycast(ray, hitResult)) {
          this._updateRaycast(<Component>hitResult.component, pointer);
          return;
        }
      }

      const cameras = componentsManager._activeCameras;
      for (let j = cameras.length - 1; j >= 0; j--) {
        const camera = cameras.get(j);
        if (camera.renderTarget) continue;
        const { pixelViewport } = camera;
        if (
          x < pixelViewport.x ||
          y < pixelViewport.y ||
          x > pixelViewport.x + pixelViewport.width ||
          y > pixelViewport.y + pixelViewport.height
        ) {
          continue;
        }
        camera.screenPointToRay(pointer.position, ray);

        /** Other canvases */
        const cameraPosition = camera.entity.transform.position;
        /** Sort by rendering order */
        canvasElements = componentsManager._canvases;
        for (let k = 0, n = canvasElements.length; k < n; k++) {
          canvasElements.get(k)._updateSortDistance(cameraPosition);
        }
        canvasElements.sort((a, b) => a.sortOrder - b.sortOrder || a._sortDistance - b._sortDistance);
        for (let k = 0, n = canvasElements.length; k < n; k++) {
          canvasElements.get(k)._canvasIndex = k;
        }
        const farClipPlane = camera.farClipPlane;
        /** Post-rendering first detection */
        for (let k = 0, n = canvasElements.length; k < n; k++) {
          const canvas = canvasElements.get(k);
          if (canvas.renderCamera !== camera) continue;
          if (canvas.raycast(ray, hitResult, farClipPlane)) {
            this._updateRaycast(<Component>hitResult.component, pointer);
            return;
          }
        }
        if (camera.clearFlags & CameraClearFlags.Color) {
          this._updateRaycast(null);
          return;
        }
      }
    }
    return;
🧰 Tools
🪛 Biome

[error] 89-89: The function should not return a value because its return type is void.

The function is here:

'void' signals the absence of value. The returned value is likely to be ignored by the caller.

(lint/correctness/noVoidTypeReturn)

packages/ui/src/component/UIRenderer.ts (5)

134-135: 🛠️ Refactor suggestion

Avoid suppressing TypeScript errors with // @ts-ignore

The use of // @ts-ignore suppresses TypeScript compiler errors, which can mask underlying issues in the code. It's important to address the root cause of the TypeScript error to ensure type safety and maintainability.

Consider adding a proper type declaration for _componentType:

// Before the constructor, add the property declaration
protected _componentType: ComponentType;

This will eliminate the need for // @ts-ignore and make the code more robust.


139-139: 🛠️ Refactor suggestion

Avoid direct access to internal properties

Directly accessing internal properties like _onValueChanged can break encapsulation and lead to maintenance challenges. It's better to use public methods or properties provided by the class.

Consider adding a method to Color class to handle the value change subscription:

// In Color class
public set onValueChanged(callback: () => void) {
  this._onValueChanged = callback;
}

And then update your code:

-    //@ts-ignore
-    this._color._onValueChanged = this._onColorChange;
+    this._color.onValueChanged = this._onColorChange;

292-292: 🛠️ Refactor suggestion

Avoid direct assignment to internal properties

Setting internal properties like _onValueChanged to null directly can cause unexpected behavior. Provide a proper method to unsubscribe or dispose of event handlers.

Modify the Color class to include an unsubscribe method:

// In Color class
public clearOnValueChanged(): void {
  this._onValueChanged = null;
}

And update your code accordingly:

-    //@ts-ignore
-    this._color._onValueChanged = null;
+    this._color.clearOnValueChanged();

173-175: 🛠️ Refactor suggestion

Resolve TypeScript issues instead of using // @ts-ignore

Using // @ts-ignore bypasses TypeScript's type checking, which can lead to runtime errors. It's crucial to address the type incompatibilities to maintain type safety.

Investigate the types of this.shaderData._macroCollection and this._globalShaderMacro to ensure they match the expected parameters of ShaderMacroCollection.unionCollection. Update the type definitions if necessary to align with the method's requirements.


214-215: ⚠️ Potential issue

Refactor assignment in expression for clarity

Using an assignment within a logical expression can be confusing and may lead to unintended side effects. At line 215, the assignment _hierarchyDirty = true is made within a logical AND operation. This can be refactored for better readability and to follow best practices.

Apply this diff to improve the code clarity:

       const rootCanvas = this._canvas;
-      rootCanvas && (rootCanvas._hierarchyDirty = true);
+      if (rootCanvas) {
+        rootCanvas._hierarchyDirty = true;
+      }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

      const rootCanvas = this._canvas;
      if (rootCanvas) {
        rootCanvas._hierarchyDirty = true;
      }
🧰 Tools
🪛 Biome

[error] 215-215: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/ui/src/component/advanced/Image.ts (2)

150-150: ⚠️ Potential issue

Undefined method _onColorChange

The method _onColorChange is referenced but not defined in the Image class. This will cause a runtime error when _onColorChange is called.

Consider adding the _onColorChange method to handle updates when the color changes. Here's a possible implementation:

private _onColorChange(): void {
  this._dirtyUpdateFlag |= UIRendererUpdateFlags.Color;
}

146-147: 🛠️ Refactor suggestion

Avoid using @ts-ignore by addressing TypeScript type issues

Using @ts-ignore suppresses TypeScript errors, potentially hiding bugs and undermining type safety. It's better to resolve the underlying type issues to ensure code reliability.

Review the following instances where @ts-ignore is used:

  • Lines 123-125 and 129-131: Accessing _updateFlagManager on Sprite.
  • Lines 146-147 and 218-219: Accessing _engine._basicResources.uiDefaultMaterial.
  • Lines 149-150: Assigning to this._color._onValueChanged.

Consider the following actions:

  • Expose necessary properties or methods: If properties like _updateFlagManager and _basicResources are needed externally, provide public getters or methods in their respective classes.
  • Adjust TypeScript definitions: Ensure all accessed properties and methods are correctly typed and accessible according to their visibility (public, protected, private).
  • Refactor code: Modify the design to eliminate the need for accessing internal properties or using @ts-ignore.

Also applies to: 149-150, 123-125, 129-131, 218-219

packages/ui/src/component/advanced/Label.ts (6)

228-228: 🛠️ Refactor suggestion

Avoid assignments within expressions for clarity

Using assignments within expressions can make the code harder to read and maintain. Consider refactoring the assignment for better readability.

-        this._subFont && (this._subFont = null);
+        if (this._subFont) {
+          this._subFont = null;
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    if (this._subFont) {
      this._subFont = null;
    }
🧰 Tools
🪛 Biome

[error] 228-228: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


458-458: 🛠️ Refactor suggestion

Avoid assignments within expressions for clarity

For better code readability, avoid assigning values within expressions. Refactor to use an if statement.

-              j === firstRow && (minX = Math.min(minX, left));
+              if (j === firstRow) {
+                minX = Math.min(minX, left);
+              }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

              if (j === firstRow) {
                minX = Math.min(minX, left);
              }
🧰 Tools
🪛 Biome

[error] 458-458: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


456-456: 🛠️ Refactor suggestion

Avoid assignments within expressions for clarity

Assignments within expressions can reduce code clarity. Use an explicit conditional statement instead.

-              i === firstLine && (maxY = Math.max(maxY, top));
+              if (i === firstLine) {
+                maxY = Math.max(maxY, top);
+              }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

              if (i === firstLine) {
                maxY = Math.max(maxY, top);
              }
🧰 Tools
🪛 Biome

[error] 456-456: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


445-446: 🛠️ Refactor suggestion

Avoid assignments within expressions for clarity

The assignment inside the logical expression can be confusing. Refactor to an explicit if statement for improved readability.

              if (charInfo.h > 0) {
-                firstRow < 0 && (firstRow = j);
+                if (firstRow < 0) {
+                  firstRow = j;
+                }

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Biome

[error] 445-445: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 446-446: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


231-234: 🛠️ Refactor suggestion

Avoid suppressing TypeScript errors with @ts-ignore

Suppressing TypeScript checks can mask potential issues. Refactor the code to address the type errors without using @ts-ignore.

-      // @ts-ignore
       override _cloneTo(target: Label, srcRoot: Entity, targetRoot: Entity): void {
-        // @ts-ignore
         super._cloneTo(target, srcRoot, targetRoot);
         target.font = this._font;
       }

Ensure that method overrides correctly match the base class signatures and that types are properly defined.

Committable suggestion skipped: line range outside the PR's diff.


207-210: 🛠️ Refactor suggestion

Avoid suppressing TypeScript errors with @ts-ignore

Using @ts-ignore may hide underlying issues and lead to maintenance challenges. Consider resolving the TypeScript errors directly or adjusting the access modifiers.

-        // @ts-ignore
         this.font = engine._textDefaultFont;
-        // @ts-ignore
         this.setMaterial(engine._basicResources.textDefaultMaterial);

If accessing private or protected members, consider exposing them through public getters or adjusting the class design.

Committable suggestion skipped: line range outside the PR's diff.

packages/ui/src/component/UICanvas.ts (6)

587-588: ⚠️ Potential issue

Add a break statement to prevent unintended fall-through

In the switch statement at line 586, the case CanvasRenderMode.ScreenSpaceOverlay lacks a break, causing it to fall through to the next case unintentionally.

Apply this diff to add the missing break:

            case CanvasRenderMode.ScreenSpaceOverlay:
              this._removeCanvasListener();
+             break;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        case CanvasRenderMode.ScreenSpaceOverlay:
          this._removeCanvasListener();
          break;
🧰 Tools
🪛 Biome

[error] 587-588: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


159-159: 🛠️ Refactor suggestion

Avoid assignment within a logical expression for better readability

On line 159, assigning this.scene._componentsManager._overlayCanvasesSortingFlag = true within a logical expression can be confusing and reduce code clarity.

Apply this diff to improve readability:

         this._realRenderMode === CanvasRenderMode.ScreenSpaceOverlay &&
           // @ts-ignore
-          (this.scene._componentsManager._overlayCanvasesSortingFlag = true);
+          this.scene._componentsManager._overlayCanvasesSortingFlag = true;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        this.scene._componentsManager._overlayCanvasesSortingFlag = true;
🧰 Tools
🪛 Biome

[error] 159-159: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


597-598: ⚠️ Potential issue

Add a break statement to prevent unintended fall-through

At line 597, the case CanvasRenderMode.ScreenSpaceOverlay is missing a break statement, leading to unintended execution of subsequent cases.

Apply this diff to correct the switch case:

            case CanvasRenderMode.ScreenSpaceOverlay:
              this._addCanvasListener();
+             break;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        case CanvasRenderMode.ScreenSpaceOverlay:
          this._addCanvasListener();
          break;
🧰 Tools
🪛 Biome

[error] 597-598: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


599-601: ⚠️ Potential issue

Add a break statement to prevent unintended fall-through

The case CanvasRenderMode.ScreenSpaceCamera at line 599 lacks a break, causing unintended fall-through to the next case.

Apply this diff to add the missing break:

            case CanvasRenderMode.ScreenSpaceCamera:
              this._adapterPoseInScreenSpace();
              this._adapterSizeInScreenSpace();
+             break;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        case CanvasRenderMode.ScreenSpaceCamera:
          this._adapterPoseInScreenSpace();
          this._adapterSizeInScreenSpace();
          break;
🧰 Tools
🪛 Biome

[error] 599-601: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


406-408: ⚠️ Potential issue

Add a break statement to prevent unintended fall-through

The case ComponentType.UIRenderer lacks a break statement, causing it to fall through to ComponentType.UIInteractive, which may not be the intended behavior.

Apply this diff to add the missing break:

            case ComponentType.UIRenderer:
              elements[depth] = component as unknown as IGraphics;
              ++depth;
+             break;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        case ComponentType.UIRenderer:
          elements[depth] = component as unknown as IGraphics;
          ++depth;
          break;
🧰 Tools
🪛 Biome

[error] 406-408: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


237-237: ⚠️ Potential issue

Wrap variable declarations in switch cases with blocks to prevent scope issues

Declaring variables directly within a switch case can cause them to be accessible in other cases due to JavaScript's function-level scoping.

Apply this diff to encapsulate the declaration:

              case CanvasRenderMode.ScreenSpaceOverlay:
+               {
                  const { min, max } = renderer.bounds;
                  if (min.x > width || max.x < 0 || min.y > height || max.y < 0) {
                    continue;
                  }
                  break;
+               }

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Biome

[error] 237-237: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

packages/design/src/ui/IUIElement.ts (1)

1-4: 🛠️ Refactor suggestion

Consider revising member naming conventions and visibility.

The interface members use underscore prefix suggesting they are private/internal, which is unconventional for interface contracts. Interface members typically define the public API contract.

Consider this alternative approach:

 export interface IUIElement {
-  _onUIUpdateIndex: number;
-  _onUpdate(): void;
+  updateIndex: number;
+  update(): void;
 }

Additionally, please add JSDoc comments to document:

  • The purpose of this interface
  • The role of updateIndex in controlling update order/priority
  • When and how the update method is called

Committable suggestion skipped: line range outside the PR's diff.

packages/design/src/ui/IUICanvas.ts (2)

2-2: 🛠️ Refactor suggestion

⚠️ Potential issue

Replace any types with specific types for better type safety.

Using any type bypasses TypeScript's type checking and can lead to runtime errors. Consider defining proper types for these members.

Consider defining specific types:

import { Entity } from '../core/Entity';
import { RenderContext } from '../renderer/RenderContext';
import { RenderElement } from '../renderer/RenderElement';

export interface UICanvas {
  entity: Entity;
  // ... other members
  _renderElement: RenderElement;
  _prepareRender(renderContext: RenderContext): void;
}

Also applies to: 5-5, 6-6


4-5: 🛠️ Refactor suggestion

Remove private members from the interface definition.

The interface includes members prefixed with underscore, suggesting they are private implementation details. Interfaces should define the public contract, not implementation details.

Consider moving private members to the implementing class:

export interface UICanvas {
  entity: Entity;
  sortOrder: number;
  prepareRender(renderContext: RenderContext): void;
}

// In the implementing class:
class Canvas implements UICanvas {
  private _canvasIndex: number;
  private _renderElement: RenderElement;
  // ... implementation
}
packages/core/src/2d/text/ITextRenderer.ts (1)

4-8: 🛠️ Refactor suggestion

Consider removing underscore prefix from public interface method

The method _getSubFont() uses an underscore prefix, which typically indicates a private or internal member. However, this is part of a public interface and TypeScript has proper visibility modifiers that should be used instead.

Consider this alternative:

 export interface ITextRenderer {
   text: string;
   overflowMode: OverflowMode;
-  _getSubFont(): SubFont;
+  getSubFont(): SubFont;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

export interface ITextRenderer {
  text: string;
  overflowMode: OverflowMode;
  getSubFont(): SubFont;
}
packages/ui/src/interface/IGraphics.ts (1)

8-8: 🛠️ Refactor suggestion

Consider a more TypeScript-idiomatic approach for the raycast method.

Several concerns with the current method signature:

  1. The underscore prefix suggests an internal implementation, but TypeScript has built-in visibility modifiers that could be more appropriate.
  2. Using an out parameter (HitResult) is not a typical TypeScript pattern. Consider returning a HitResult | null instead.
  3. The purpose of the distance parameter isn't clear from the signature.

Consider this alternative approach:

-  _raycast(ray: Ray, out: HitResult, distance: number): boolean;
+  /** Performs raycasting against this graphics element
+   * @param ray The ray to test against
+   * @param maxDistance The maximum distance to test for intersections
+   * @returns The hit result if an intersection occurred, null otherwise
+   */
+  protected raycast(ray: Ray, maxDistance: number): HitResult | null;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  /** Performs raycasting against this graphics element
   * @param ray The ray to test against
   * @param maxDistance The maximum distance to test for intersections
   * @returns The hit result if an intersection occurred, null otherwise
   */
  protected raycast(ray: Ray, maxDistance: number): HitResult | null;
packages/ui/src/interface/IElement.ts (2)

8-10: 🛠️ Refactor suggestion

Consider encapsulating internal state.

Internal state properties prefixed with _ should not be exposed in the public interface. Consider:

  1. Moving these to a separate internal interface
  2. Using private fields in the implementing class
// Example approach using a separate internal interface
interface IElementInternal {
  _indexInCanvas: number;
  _isCanvasDirty: boolean;
}

export interface IElement {
  entity: Entity;
  readonly canvas: UICanvas;
}

11-12: 🛠️ Refactor suggestion

Improve type safety and encapsulation of event handling.

The current event handling implementation has several issues:

  1. The flag parameter uses a number type instead of an enum
  2. The param parameter is loosely typed as any
  3. The listener array is exposed as an internal property

Consider this improved approach:

/** Event types for canvas interactions */
export enum CanvasEventType {
  // Define your event types here
}

/** Event data structure */
export interface CanvasEventData {
  // Define strongly typed event parameters
}

export interface IElement {
  // ... other members ...
  
  /** Add a canvas event listener */
  addEventListener(entity: Entity, callback: (event: CanvasEventType, data?: CanvasEventData) => void): void;
  
  /** Remove a canvas event listener */
  removeEventListener(entity: Entity): void;
}
packages/core/src/enums/ComponentType.ts (1)

22-22: ⚠️ Potential issue

Fix incorrect bitwise combination value

The value 0x680 for UIElement appears to include an extra bit that doesn't correspond to any of its component flags. If it's meant to be a combination of UICanvas (0x80), UIGroup (0x100), and UIRenderer (0x200), the value should be 0x380.

- UIElement = 0x680
+ UIElement = 0x380  // UICanvas | UIGroup | UIRenderer
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  UIElement = 0x380  // UICanvas | UIGroup | UIRenderer
packages/ui/src/interface/IGroupAble.ts (2)

14-16: 🛠️ Refactor suggestion

Review method visibility and callback type safety

A few concerns about the method declarations:

  1. _onUpdate and _onGroupModify are internal methods but part of the public interface
  2. _groupListener uses a generic number type for the flag parameter, while _onGroupModify uses the more specific GroupModifyFlags

Consider these improvements:

export interface IGroupAble extends IElement {
  readonly group: UIGroup;
}

interface IGroupableInternal extends IGroupAble {
  _onUpdate?(): void;
  _onGroupModify(flag: GroupModifyFlags): void;
  _groupListener: (flag: GroupModifyFlags) => void; // Use specific enum type
}

1-17: 💡 Codebase verification

Based on the search results, I can now generate the final response:

Optional properties in IGroupAble are implemented differently across classes

The verification shows that these properties are not truly optional in implementations:

  • _onUIUpdateIndex is initialized to 0 in both UIInteractive and UIRenderer
  • _globalInteractive is initialized to true in UIGroup and false in UIInteractive
  • _globalAlpha is initialized to 1 in UIGroup
  • _onUpdate is implemented in UIInteractive but not in UIGroup

The interface should reflect the actual implementation requirements by removing the optional markers (?) since these properties are always present in implementing classes.

🔗 Analysis chain

Verify usage of optional properties

The interface has several optional properties (_onUIUpdateIndex, _globalAlpha, _globalInteractive, _onUpdate). We should verify if these are truly optional or if they're always set during initialization.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for implementations to verify optional property usage
ast-grep --pattern 'class $_ implements IGroupAble {
  $$$
}'

# Search for property assignments
rg -A 5 "_onUIUpdateIndex|_globalAlpha|_globalInteractive" --type typescript

Length of output: 177


Script:

#!/bin/bash
# Search for implementations using different file extensions
ast-grep --pattern 'class $_ implements IGroupAble {
  $$$
}'

# Search for property assignments with .ts and .tsx extensions
rg -t ts -t tsx -A 5 "_onUIUpdateIndex|_globalAlpha|_globalInteractive"

# Search for property initialization patterns
rg -t ts -t tsx "constructor.*IGroupAble" -A 20

# Search for files that might implement IGroupAble
fd -e ts -e tsx | xargs grep -l "implements IGroupAble"

Length of output: 393


Script:

#!/bin/bash
# Let's examine the content of the implementing files
cat packages/ui/src/component/UIGroup.ts packages/ui/src/component/interactive/UIInteractive.ts

# Search for property assignments without file type flags
rg "_onUIUpdateIndex|_globalAlpha|_globalInteractive" 

# Search for constructor initialization
rg "constructor.*{" -A 10 packages/ui/src/component/UIGroup.ts packages/ui/src/component/interactive/UIInteractive.ts

Length of output: 15476

packages/ui/src/component/interactive/transition/ScaleTransition.ts (1)

22-24: ⚠️ Potential issue

Add null check for target entity.

The _applyValue method should include a null check before accessing the target entity.

 protected override _applyValue(value: number): void {
+  if (!this._target?.entity) {
+    return;
+  }
   this._target.entity.transform.setScale(value, value, value);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  protected override _applyValue(value: number): void {
    if (!this._target?.entity) {
      return;
    }
    this._target.entity.transform.setScale(value, value, value);
  }
packages/core/src/2d/index.ts (1)

3-5: 💡 Codebase verification

Documentation needed for sprite assembler system

The sprite documentation (sprite.md) lacks information about the different sprite assembler types and their use cases. While it covers sprite properties and general usage, it doesn't explain:

  • The purpose of Simple, Sliced, and Tiled sprite assemblers
  • When to use each assembler type
  • How these assemblers interact with the sprite rendering system

The implementation files also lack comprehensive documentation comments explaining their architectural roles.

Suggested documentation points:

  • Add a new section in sprite.md about sprite assembly modes
  • Include code-level documentation in assembler classes
  • Consider creating a dedicated guide for sprite rendering architecture
🔗 Analysis chain

Consider adding architectural documentation for the new assembler system.

The introduction of three distinct sprite assemblers (Simple, Sliced, Tiled) suggests a significant architectural enhancement. This warrants documentation explaining:

  • The purpose and use case for each assembler type
  • How to choose between different assemblers
  • The relationship between assemblers and renderers

Let's check if documentation exists:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Look for documentation about the assembler system

# Search for documentation files
echo "Searching for documentation files:"
fd -e md -e txt . | grep -i "assembler\|sprite\|gui"

# Search for documentation comments in assembler files
echo "Searching for documentation comments in assembler files:"
rg "\/\*\*|\*\/" "./assembler/"

Length of output: 708


Script:

#!/bin/bash
# Let's check the content of relevant documentation and assembler files

# Check English documentation content
echo "=== Sprite Documentation Content ==="
cat docs/en/graphics/2D/sprite.md

# Check assembler files for documentation
echo -e "\n=== Assembler Implementation Documentation ==="
rg -A 5 "class.*Assembler" "packages/core/src/2d/assembler/"

Length of output: 6836

packages/ui/src/component/interactive/transition/SpriteTransition.ts (1)

34-44: ⚠️ Potential issue

Add null checks and error handling for robustness.

The value management methods need additional safety checks:

  1. _getTargetValueCopy might return undefined when _target is null
  2. _updateCurrentValue lacks validation for null/undefined values
  3. _applyValue might throw if this._target is null

Consider adding these safety checks:

- protected _getTargetValueCopy(): Sprite {
-   return this._target?.sprite;
+ protected _getTargetValueCopy(): Sprite | null {
+   return this._target?.sprite ?? null;
  }

  protected override _updateCurrentValue(srcValue: Sprite, destValue: Sprite, weight: number): void {
+   if (!srcValue && !destValue) {
+     this._currentValue = null;
+     return;
+   }
    this._currentValue = weight >= 1 ? destValue : srcValue;
  }

  protected override _applyValue(value: Sprite): void {
+   if (!this._target) {
+     return;
+   }
    this._target.sprite = value || this._normal;
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  protected _getTargetValueCopy(): Sprite | null {
    return this._target?.sprite ?? null;
  }

  protected override _updateCurrentValue(srcValue: Sprite, destValue: Sprite, weight: number): void {
    if (!srcValue && !destValue) {
      this._currentValue = null;
      return;
    }
    this._currentValue = weight >= 1 ? destValue : srcValue;
  }

  protected override _applyValue(value: Sprite): void {
    if (!this._target) {
      return;
    }
    this._target.sprite = value || this._normal;
  }
packages/ui/src/component/advanced/Button.ts (2)

32-35: 🛠️ Refactor suggestion

Improve cleanup implementation and readability.

  1. The current cleanup only marks listeners as destroyed but doesn't clear the array.
  2. The assignment in expression makes the code less maintainable.

Consider this improved implementation:

  override onDestroy(): void {
    super.onDestroy();
-   this._listeners.findAndRemove((value) => (value.destroyed = true));
+   this._listeners.forEach(listener => listener.destroyed = true);
+   this._listeners.clear();
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  override onDestroy(): void {
    super.onDestroy();
    this._listeners.forEach(listener => listener.destroyed = true);
    this._listeners.clear();
  }
🧰 Tools
🪛 Biome

[error] 34-34: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


11-21: 🛠️ Refactor suggestion

Improve code readability and documentation.

  1. The assignment within the expression makes the code harder to read and maintain.
  2. The method documentation could be more descriptive about the event parameter.

Consider these improvements:

  /**
   * Add a listening function for click.
   * @param listener - The listening function
+  * @param event - The pointer event data containing click information
   */
  addClicked(listener: (event: PointerEventData) => void): void {
    this._listeners.push({ fn: listener });
  }

  /**
   * Remove a listening function of click.
   * @param listener - The listening function
+  * @returns boolean - True if the listener was found and removed
   */
  removeClicked(listener: (event: PointerEventData) => void): void {
-   this._listeners.findAndRemove((value) => (value.fn === listener ? (value.destroyed = true) : false));
+   this._listeners.findAndRemove((value) => {
+     if (value.fn === listener) {
+       value.destroyed = true;
+       return true;
+     }
+     return false;
+   });
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  /**
   * Add a listening function for click.
   * @param listener - The listening function
   * @param event - The pointer event data containing click information
   */
  addClicked(listener: (event: PointerEventData) => void): void {
    this._listeners.push({ fn: listener });
  }

  /**
   * Remove a listening function of click.
   * @param listener - The listening function
   * @returns boolean - True if the listener was found and removed
   */
  removeClicked(listener: (event: PointerEventData) => void): void {
    this._listeners.findAndRemove((value) => {
      if (value.fn === listener) {
        value.destroyed = true;
        return true;
      }
      return false;
    });
  }
🧰 Tools
🪛 Biome

[error] 20-20: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/ui/UIUtils.ts (2)

10-14: 🛠️ Refactor suggestion

Consider refactoring to a module pattern with initialized fields

The current implementation as a static-only class could be improved by converting it to a module with standalone functions and properly initialized fields. This would align with TypeScript best practices and prevent potential race conditions.

-export class UIUtils {
-  private static _renderQueue: RenderQueue;
-  private static _virtualCamera: VirtualCamera;
-  private static _viewport: Vector4;
+const _renderQueue = new RenderQueue(RenderQueueType.Transparent);
+const _virtualCamera = new VirtualCamera();
+const _viewport = new Vector4(0, 0, 1, 1);
+
+export function render(engine: Engine, uiCanvases: DisorderedArray<IUICanvas>): void {
   // ... implementation
-}

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Biome

[error] 10-39: Avoid classes that contain only static members.

Prefer using simple functions instead of classes with only static members.

(lint/complexity/noStaticOnlyClass)


15-19: 🛠️ Refactor suggestion

Improve initialization pattern and add input validation

The current initialization pattern uses assignments within expressions and lacks input validation. This can be improved for better readability and robustness.

 static render(engine: Engine, uiCanvases: DisorderedArray<IUICanvas>): void {
+  if (!engine || !uiCanvases) {
+    throw new Error("Engine and uiCanvases parameters are required");
+  }
+
-  const uiRenderQueue = (this._renderQueue ||= new RenderQueue(RenderQueueType.Transparent));
-  const virtualCamera = (this._virtualCamera ||= new VirtualCamera());
-  const viewport = (this._viewport ||= new Vector4(0, 0, 1, 1));
+  const uiRenderQueue = UIUtils._renderQueue;
+  const virtualCamera = UIUtils._virtualCamera;
+  const viewport = UIUtils._viewport;

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Biome

[error] 16-16: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 16-16: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 17-17: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 17-17: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 18-18: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 18-18: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)

packages/ui/src/component/interactive/transition/ColorTransition.ts (2)

22-29: ⚠️ Potential issue

Address type safety concerns with @ts-ignore.

Multiple @ts-ignore comments suggest potential type safety issues. Consider:

  1. Properly typing the Color class to include _onValueChanged
  2. Using a proper event emitter pattern instead of direct property assignment
// Example type definition to consider
interface TransitionColor extends Color {
  _onValueChanged?: () => void;
}

48-64: ⚠️ Potential issue

Add null safety checks in protected methods.

Several methods could benefit from additional null safety checks:

  1. _getTargetValueCopy: The optional chaining on _target?.color is good, but reusing this._color for multiple operations could lead to side effects
  2. _applyValue: Missing null check for this._target
 protected _getTargetValueCopy(): Color {
-  const color = this._color;
+  const color = new Color();
   color.copyFrom(this._target?.color || this._normal);
   return color;
 }

 protected override _applyValue(value: Color): void {
+  if (!this._target) {
+    return;
+  }
   this._target.color = value;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  protected _getTargetValueCopy(): Color {
    const color = new Color();
    color.copyFrom(this._target?.color || this._normal);
    return color;
  }

  protected override _updateCurrentValue(srcValue: Color, destValue: Color, weight: number): void {
    if (weight >= 1) {
      this._currentValue.copyFrom(destValue);
    } else {
      Color.lerp(srcValue, destValue, weight, this._currentValue);
    }
  }

  protected override _applyValue(value: Color): void {
    if (!this._target) {
      return;
    }
    this._target.color = value;
  }
packages/core/src/ComponentsDependencies.ts (2)

97-101: 🛠️ Refactor suggestion

Add JSDoc documentation and type validation

The decorator lacks documentation and type validation. Consider:

  1. Adding JSDoc documentation to explain its purpose and usage
  2. Adding validation to ensure the target is a valid component constructor
+/**
+ * Decorator that marks a component to be automatically inherited by child entities.
+ * Used primarily for GUI components that should propagate through the entity hierarchy.
+ * @example
+ * ```ts
+ * @markAsInherited()
+ * class UIComponent extends Component {
+ *   // ...
+ * }
+ * ```
+ */
 export function markAsInherited() {
   return function <T extends ComponentConstructor>(target: T): void {
+    if (!(target.prototype instanceof Component)) {
+      throw new Error(`${target.name} must be a Component class`);
+    }
     ComponentsDependencies._inheritedMap.set(target, true);
   };
 }

14-14: 🛠️ Refactor suggestion

Use lowercase boolean type instead of Boolean

For consistency with TypeScript best practices, use the primitive type boolean instead of the object wrapper type Boolean.

- static _inheritedMap = new Map<ComponentConstructor, Boolean>();
+ static _inheritedMap = new Map<ComponentConstructor, boolean>();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  static _inheritedMap = new Map<ComponentConstructor, boolean>();
🧰 Tools
🪛 Biome

[error] 14-14: Don't use 'Boolean' as a type.

Use lowercase primitives for consistency.
Safe fix: Use 'boolean' instead

(lint/complexity/noBannedTypes)

packages/core/src/2d/assembler/SlicedSpriteAssembler.ts (2)

65-69: 🛠️ Refactor suggestion

Improve readability by separating combined assignments

The combined assignment statements make the code harder to read and maintain. Consider breaking them down into separate lines.

Apply this diff to improve readability:

- (row[0] = expectWidth * left * widthScale), (row[1] = row[2] = fixedLeft * widthScale);
- row[3] = width - expectWidth * (1 - right) * widthScale;
+ row[0] = expectWidth * left * widthScale;
+ row[1] = fixedLeft * widthScale;
+ row[2] = fixedLeft * widthScale;
+ row[3] = width - expectWidth * (1 - right) * widthScale;

- (row[0] = expectWidth * left), (row[1] = fixedLeft), (row[2] = width - fixedRight);
- row[3] = width - expectWidth * (1 - right);
+ row[0] = expectWidth * left;
+ row[1] = fixedLeft;
+ row[2] = width - fixedRight;
+ row[3] = width - expectWidth * (1 - right);

- (column[0] = expectHeight * bottom * heightScale), (column[1] = column[2] = fixedBottom * heightScale);
- column[3] = height - expectHeight * (1 - top) * heightScale;
+ column[0] = expectHeight * bottom * heightScale;
+ column[1] = fixedBottom * heightScale;
+ column[2] = fixedBottom * heightScale;
+ column[3] = height - expectHeight * (1 - top) * heightScale;

- (column[0] = expectHeight * bottom), (column[1] = fixedBottom), (column[2] = height - fixedTop);
- column[3] = height - expectHeight * (1 - top);
+ column[0] = expectHeight * bottom;
+ column[1] = fixedBottom;
+ column[2] = height - fixedTop;
+ column[3] = height - expectHeight * (1 - top);

Also applies to: 74-78


169-173: 🛠️ Refactor suggestion

Improve readability of UV calculations

Similar to earlier feedback, the combined assignment statements here should be separated for better readability.

Apply this diff:

- (row[0] = expectWidth * left * widthScale), (row[1] = row[2] = fixedLeft * widthScale);
- row[3] = width - expectWidth * (1 - right) * widthScale;
+ row[0] = expectWidth * left * widthScale;
+ row[1] = fixedLeft * widthScale;
+ row[2] = fixedLeft * widthScale;
+ row[3] = width - expectWidth * (1 - right) * widthScale;

- (row[0] = expectWidth * left), (row[1] = fixedLeft), (row[2] = width - fixedRight);
- row[3] = width - expectWidth * (1 - right);
+ row[0] = expectWidth * left;
+ row[1] = fixedLeft;
+ row[2] = width - fixedRight;
+ row[3] = width - expectWidth * (1 - right);

- (column[0] = expectHeight * bottom * heightScale), (column[1] = column[2] = fixedBottom * heightScale);
- column[3] = height - expectHeight * (1 - top) * heightScale;
+ column[0] = expectHeight * bottom * heightScale;
+ column[1] = fixedBottom * heightScale;
+ column[2] = fixedBottom * heightScale;
+ column[3] = height - expectHeight * (1 - top) * heightScale;

- (column[0] = expectHeight * bottom), (column[1] = fixedBottom), (column[2] = height - fixedTop);
- column[3] = height - expectHeight * (1 - top);
+ column[0] = expectHeight * bottom;
+ column[1] = fixedBottom;
+ column[2] = height - fixedTop;
+ column[3] = height - expectHeight * (1 - top);

Also applies to: 178-182

packages/core/src/ComponentsManager.ts (2)

272-276: 🛠️ Refactor suggestion

Align callUIOnUpdate with other update methods

The implementation is inconsistent with other update methods in the class. Other methods maintain indices during iteration.

Apply this change to maintain consistency:

 callUIOnUpdate(deltaTime: number): void {
-  this._onUpdateUIElements.forEach((element: IUIElement) => {
-    element._onUpdate();
-  });
+  this._onUpdateUIElements.forEach(
+    (element: IUIElement) => {
+      element._onUpdate();
+    },
+    (element: IUIElement, index: number) => {
+      element._onUIUpdateIndex = index;
+    }
+  );
 }

Committable suggestion skipped: line range outside the PR's diff.


185-188: ⚠️ Potential issue

Fix incorrect array reference in addOnUpdateUIElement

There's a critical bug in the implementation. The method is using this._onUpdateRenderers.length instead of this._onUpdateUIElements.length for the index calculation.

Apply this fix:

 addOnUpdateUIElement(element: IUIElement): void {
-  element._onUIUpdateIndex = this._onUpdateRenderers.length;
+  element._onUIUpdateIndex = this._onUpdateUIElements.length;
   this._onUpdateUIElements.add(element);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  addOnUpdateUIElement(element: IUIElement): void {
    element._onUIUpdateIndex = this._onUpdateUIElements.length;
    this._onUpdateUIElements.add(element);
  }
packages/core/src/2d/sprite/SpriteRenderer.ts (1)

421-451: 💡 Codebase verification

Based on the provided code snippet and the enum values found (texture, size, atlasRotate, atlasRegion, atlasRegionOffset, border), I can now generate the final response.

Missing handlers for several sprite modification flags

The current switch statement only handles size and border modifications, but the SpriteModifyFlags enum defines additional flags that should be handled:

  • texture (0x1)
  • atlasRotate (0x4)
  • atlasRegion (0x8)
  • atlasRegionOffset (0x10)
🔗 Analysis chain

Verify the completeness of sprite modification handling

While the switch statement handles various modification types, we should verify if all possible sprite modifications are covered.


Let me check the complete sprite modification handling by looking at the switch statement in the file.


Let me try a different approach to find the sprite modification handling code.


Let me try one more search with a broader pattern to find the sprite modification handling.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for all possible sprite modification flags
rg -A 5 "enum SpriteModifyFlags" 

Length of output: 455


Script:

#!/bin/bash
# Search for the switch statement handling sprite modifications
rg -B 5 -A 30 "switch.*modifyFlags" packages/core/src/2d/sprite/SpriteRenderer.ts

Length of output: 83


Script:

#!/bin/bash
# Search for the sprite modification handling in SpriteRenderer
rg -B 5 -A 30 "_onSprite.*Modified" packages/core/src/2d/sprite/SpriteRenderer.ts

Length of output: 83


Script:

#!/bin/bash
# Search for any method handling sprite modifications in SpriteRenderer
rg -B 5 -A 30 "modifyFlags" packages/core/src/2d/sprite/SpriteRenderer.ts

Length of output: 73

packages/core/src/2d/assembler/TiledSpriteAssembler.ts (2)

81-86: ⚠️ Potential issue

Replace global isNaN with Number.isNaN

The global isNaN function performs type coercion which can lead to unexpected results. Use Number.isNaN instead for more precise NaN checking.

-      if (isNaN(cUV.get(doubleJ)) || isNaN(cUV.get(doubleJ + 1))) {
+      if (Number.isNaN(cUV.get(doubleJ)) || Number.isNaN(cUV.get(doubleJ + 1))) {
-        if (isNaN(rUV.get(doubleI)) || isNaN(rUV.get(doubleI + 1))) {
+        if (Number.isNaN(rUV.get(doubleI)) || Number.isNaN(rUV.get(doubleI + 1))) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

      if (Number.isNaN(cUV.get(doubleJ)) || Number.isNaN(cUV.get(doubleJ + 1))) {
        continue;
      }
      for (let i = 0; i < rowLength; i++) {
        const doubleI = i << 1;
        if (Number.isNaN(rUV.get(doubleI)) || Number.isNaN(rUV.get(doubleI + 1))) {
🧰 Tools
🪛 Biome

[error] 81-81: isNaN is unsafe. It attempts a type coercion. Use Number.isNaN instead.

See the MDN documentation for more details.
Unsafe fix: Use Number.isNaN instead.

(lint/suspicious/noGlobalIsNan)


[error] 81-81: isNaN is unsafe. It attempts a type coercion. Use Number.isNaN instead.

See the MDN documentation for more details.
Unsafe fix: Use Number.isNaN instead.

(lint/suspicious/noGlobalIsNan)


[error] 86-86: isNaN is unsafe. It attempts a type coercion. Use Number.isNaN instead.

See the MDN documentation for more details.
Unsafe fix: Use Number.isNaN instead.

(lint/suspicious/noGlobalIsNan)


[error] 86-86: isNaN is unsafe. It attempts a type coercion. Use Number.isNaN instead.

See the MDN documentation for more details.
Unsafe fix: Use Number.isNaN instead.

(lint/suspicious/noGlobalIsNan)


422-444: ⚠️ Potential issue

Add block scoping to switch cases

Variables declared in switch cases can leak into other cases. Wrap case blocks in curly braces to prevent variable scope leakage.

     switch (rType) {
       case TiledType.Compressed: {
         const scale = width / fixedLR;
         rPos.add(expectWidth * left * scale), rPos.add(fixedL * scale);
         rPos.add(width - expectWidth * (1 - right) * scale);
         rUV.add(spriteUV0.x), rUV.add(spriteUV1.x), rUV.add(spriteUV2.x), rUV.add(spriteUV3.x);
         break;
+      }
       case TiledType.WithoutTiled: {
         // ... existing code ...
         break;
+      }
       case TiledType.WithTiled: {
         // ... existing code ...
         break;
+      }

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Biome

[error] 422-422: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 434-434: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 435-435: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 436-436: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

packages/core/src/Entity.ts (1)

613-628: 🛠️ Refactor suggestion

Refactor assignment in expression for better readability.

The modification listener system is well-designed, but the assignment in expression should be refactored for clarity.

Apply this diff to improve readability:

  _registerModifyListener(onChange: (flag: EntityModifyFlags) => void): void {
-   (this._updateFlagManager ||= new UpdateFlagManager()).addListener(onChange);
+   if (!this._updateFlagManager) {
+     this._updateFlagManager = new UpdateFlagManager();
+   }
+   this._updateFlagManager.addListener(onChange);
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  _registerModifyListener(onChange: (flag: EntityModifyFlags) => void): void {
    if (!this._updateFlagManager) {
      this._updateFlagManager = new UpdateFlagManager();
    }
    this._updateFlagManager.addListener(onChange);
  }

  /**
   * @internal
   */
  _unRegisterModifyListener(onChange: (flag: EntityModifyFlags) => void): void {
    this._updateFlagManager?.removeListener(onChange);
  }

  /**
   * @internal
   */
  _dispatchModify(flag: EntityModifyFlags, param?: any): void {
    this._updateFlagManager?.dispatch(flag, param);
🧰 Tools
🪛 Biome

[error] 614-614: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/Camera.ts (1)

738-747: 🛠️ Refactor suggestion

Refactor the assignment in expression

The assignment within the expression can make the code harder to understand and maintain.

Consider refactoring to improve readability:

-  (this._updateFlagManager ||= new UpdateFlagManager()).addListener(onChange);
+  if (!this._updateFlagManager) {
+    this._updateFlagManager = new UpdateFlagManager();
+  }
+  this._updateFlagManager.addListener(onChange);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  _registerModifyListener(onChange: (flag: CameraModifyFlags) => void): void {
    if (!this._updateFlagManager) {
      this._updateFlagManager = new UpdateFlagManager();
    }
    this._updateFlagManager.addListener(onChange);
  }

  /**
   * @internal
   */
  _unRegisterModifyListener(onChange: (flag: CameraModifyFlags) => void): void {
    this._updateFlagManager?.removeListener(onChange);
  }
🧰 Tools
🪛 Biome

[error] 739-739: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

fix lint error
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (7)
packages/ui/package.json (1)

26-30: Review completeness of included files

The files array should include all necessary files while excluding development files.

Consider adding the following to ensure proper packaging:

  • Add README.md to the files array for npm registry documentation
  • Add LICENSE file reference
  • Consider adding package.json explicitly
  "files": [
    "dist/**/*",
    "libs/**/*",
    "types/**/*",
+   "README.md",
+   "LICENSE",
+   "package.json"
  ],
packages/core/src/animation/Keyframe.ts (2)

13-22: Fix indentation according to project style guidelines.

The conditional type expressions need consistent indentation to match the project's formatting rules.

Apply this formatting fix:

    ? Vector2
-    : V extends Vector3
-    ? Vector3
-    : V extends Vector4 | Color | Quaternion | Rect
-    ? Vector4
-    : V extends number[] | Float32Array
-    ? number[]
-    : V extends ReferResource
-    ? ReferResource
-    : never
+  : V extends Vector3
+  ? Vector3
+  : V extends Vector4 | Color | Quaternion | Rect
+    ? Vector4
+  : V extends number[] | Float32Array
+    ? number[]
+  : V extends ReferResource
+    ? ReferResource
+  : never
🧰 Tools
🪛 eslint

[error] 13-13: Insert ··

(prettier/prettier)


[error] 14-14: Insert ··

(prettier/prettier)


[error] 15-15: Insert ····

(prettier/prettier)


[error] 16-16: Insert ····

(prettier/prettier)


[error] 17-17: Replace ···· with ··········

(prettier/prettier)


[error] 18-18: Insert ······

(prettier/prettier)


[error] 19-19: Insert ········

(prettier/prettier)


[error] 20-20: Insert ········

(prettier/prettier)


[error] 21-21: Insert ··········

(prettier/prettier)


[error] 22-22: Insert ··········

(prettier/prettier)

🪛 GitHub Check: lint

[failure] 13-13:
Insert ··


[failure] 14-14:
Insert ··


[failure] 15-15:
Insert ····


[failure] 16-16:
Insert ····


[failure] 17-17:
Replace ···· with ··········


[failure] 18-18:
Insert ······


[failure] 19-19:
Insert ········


[failure] 20-20:
Insert ········


[failure] 21-21:
Insert ··········


[failure] 22-22:
Insert ··········


Line range hint 1-22: LGTM! Well-structured type definition.

The generic type parameter T effectively maps input types to their appropriate value types, maintaining type safety throughout the animation system. The conditional type expressions handle all possible KeyframeValueType variants correctly.

Consider documenting the type mapping logic in the class JSDoc to help other developers understand the relationship between V and T type parameters.

🧰 Tools
🪛 eslint

[error] 13-13: Insert ··

(prettier/prettier)


[error] 14-14: Insert ··

(prettier/prettier)


[error] 15-15: Insert ····

(prettier/prettier)


[error] 16-16: Insert ····

(prettier/prettier)


[error] 17-17: Replace ···· with ··········

(prettier/prettier)


[error] 18-18: Insert ······

(prettier/prettier)


[error] 19-19: Insert ········

(prettier/prettier)


[error] 20-20: Insert ········

(prettier/prettier)


[error] 21-21: Insert ··········

(prettier/prettier)


[error] 22-22: Insert ··········

(prettier/prettier)

🪛 GitHub Check: lint

[failure] 13-13:
Insert ··


[failure] 14-14:
Insert ··


[failure] 15-15:
Insert ····


[failure] 16-16:
Insert ····


[failure] 17-17:
Replace ···· with ··········


[failure] 18-18:
Insert ······


[failure] 19-19:
Insert ········


[failure] 20-20:
Insert ········


[failure] 21-21:
Insert ··········


[failure] 22-22:
Insert ··········

packages/core/src/animation/internal/animationCurveOwner/AnimationCurveOwner.ts (2)

95-96: Consider refactoring nested ternary expressions for better readability.

While the logic is correct, the nested ternary expressions make the code harder to maintain. Consider extracting this logic into a separate method or using if-else statements.

Fix the indentation and refactor for better readability:

-        ? this.cureType._getZeroValue(this.baseEvaluateData.value)
-        : this.defaultValue;
+          ? this.cureType._getZeroValue(this.baseEvaluateData.value)
+          : this.defaultValue;

Alternative implementation using a method:

private _getFallbackValue(evaluateData: IEvaluateData<V>, additive: boolean): V {
  return additive 
    ? this.cureType._getZeroValue(evaluateData.value)
    : this.defaultValue;
}
🧰 Tools
🪛 eslint

[error] 95-95: Insert ··

(prettier/prettier)


[error] 96-96: Insert ··

(prettier/prettier)


104-105: Apply consistent formatting and refactoring.

For consistency with the previous changes, apply the same formatting and consider using the suggested _getFallbackValue method here as well.

-        ? this.cureType._getZeroValue(this.crossEvaluateData.value)
-        : this.defaultValue;
+          ? this.cureType._getZeroValue(this.crossEvaluateData.value)
+          : this.defaultValue;
🧰 Tools
🪛 eslint

[error] 104-104: Insert ··

(prettier/prettier)


[error] 105-105: Insert ··

(prettier/prettier)

packages/core/src/RenderPipeline/BasicRenderPipeline.ts (1)

346-362: Consider caching bounds check results

The bounds intersection check against the frustum is performed for every renderer on each frame. For static objects that don't move, this check could be cached until the object or camera moves.

Example optimization:

 if (enableFrustumCulling) {
-  if (!frustum.intersectsBox(renderer.bounds)) {
+  if (!renderer._isMoving && renderer._cachedFrustumResult !== undefined) {
+    if (!renderer._cachedFrustumResult) continue;
+  } else {
+    renderer._cachedFrustumResult = frustum.intersectsBox(renderer.bounds);
+    if (!renderer._cachedFrustumResult) continue;
+  }
   continue;
 }
packages/core/src/Engine.ts (1)

518-522: Consider performance optimization for UI rendering

While the UI rendering implementation is correct, consider these performance optimizations:

  1. Cache the length check result of uiCanvas to avoid multiple array access
  2. Consider adding a dirty flag system to skip UI sorting and rendering when no changes occurred

Here's a suggested optimization:

-      const uiCanvas = componentsManager._overlayCanvases;
-      if (uiCanvas.length > 0) {
-        componentsManager.sortUICanvases();
-        UIUtils.render(this, uiCanvas);
-      }
+      const uiCanvases = componentsManager._overlayCanvases;
+      const uiCanvasCount = uiCanvases.length;
+      if (uiCanvasCount > 0) {
+        if (componentsManager._isUIDirty) {
+          componentsManager.sortUICanvases();
+          componentsManager._isUIDirty = false;
+        }
+        UIUtils.render(this, uiCanvases);
+      }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 3a29609 and c4ace76.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (7)
  • packages/core/src/BasicResources.ts (3 hunks)
  • packages/core/src/Engine.ts (4 hunks)
  • packages/core/src/RenderPipeline/BasicRenderPipeline.ts (2 hunks)
  • packages/core/src/animation/Keyframe.ts (1 hunks)
  • packages/core/src/animation/internal/animationCurveOwner/AnimationCurveOwner.ts (2 hunks)
  • packages/core/src/particle/ParticleRenderer.ts (3 hunks)
  • packages/ui/package.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/core/src/BasicResources.ts
  • packages/core/src/particle/ParticleRenderer.ts
🧰 Additional context used
🪛 eslint
packages/core/src/animation/Keyframe.ts

[error] 13-13: Insert ··

(prettier/prettier)


[error] 14-14: Insert ··

(prettier/prettier)


[error] 15-15: Insert ····

(prettier/prettier)


[error] 16-16: Insert ····

(prettier/prettier)


[error] 17-17: Replace ···· with ··········

(prettier/prettier)


[error] 18-18: Insert ······

(prettier/prettier)


[error] 19-19: Insert ········

(prettier/prettier)


[error] 20-20: Insert ········

(prettier/prettier)


[error] 21-21: Insert ··········

(prettier/prettier)


[error] 22-22: Insert ··········

(prettier/prettier)

packages/core/src/animation/internal/animationCurveOwner/AnimationCurveOwner.ts

[error] 95-95: Insert ··

(prettier/prettier)


[error] 96-96: Insert ··

(prettier/prettier)


[error] 104-104: Insert ··

(prettier/prettier)


[error] 105-105: Insert ··

(prettier/prettier)


[error] 129-129: Insert ··

(prettier/prettier)


[error] 130-130: Insert ··

(prettier/prettier)

🪛 GitHub Check: lint
packages/core/src/animation/Keyframe.ts

[failure] 13-13:
Insert ··


[failure] 14-14:
Insert ··


[failure] 15-15:
Insert ····


[failure] 16-16:
Insert ····


[failure] 17-17:
Replace ···· with ··········


[failure] 18-18:
Insert ······


[failure] 19-19:
Insert ········


[failure] 20-20:
Insert ········


[failure] 21-21:
Insert ··········


[failure] 22-22:
Insert ··········

🪛 Biome
packages/core/src/animation/internal/animationCurveOwner/AnimationCurveOwner.ts

[error] 99-99: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (9)
packages/ui/package.json (3)

2-3: Verify version alignment with core package

The package version is set to 1.3.20. Since this is a new UI package that depends on @galacean/engine, we should verify version alignment.

✅ Verification successful

Version alignment verified across all Galacean packages

All Galacean packages, including the core engine and UI package, are consistently versioned at 1.3.20. This alignment is correct and follows proper monorepo versioning practices.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check version alignment between UI package and core engine
echo "Checking core engine version..."
jq -r '.version' packages/core/package.json || echo "Core package.json not found"

# List all related packages and their versions for consistency
echo -e "\nChecking all related package versions..."
fd -t f package.json -x jq -r '. | select(.name | startswith("@galacean")) | .name + ": " + .version'

Length of output: 899


31-36: Verify workspace dependency configuration

The package uses workspace dependencies for @galacean/engine in both dev and peer dependencies. This is correct for monorepo setup, but we should verify the workspace configuration.

✅ Verification successful

The workspace configuration and package manager settings look correct. However, let me verify one more thing about the dependency versions to ensure complete validation.


Workspace dependency configuration is correctly set up

The workspace configuration is properly set up with:

  • Valid pnpm workspace configuration in pnpm-workspace.yaml
  • Correct package manager specification ([email protected])
  • Consistent usage of workspace:* for @galacean/engine across multiple packages in the monorepo
  • Matching pattern of having both devDependencies and peerDependencies for the engine package
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for workspace configuration
echo "Checking workspace configuration..."
cat pnpm-workspace.yaml 2>/dev/null || cat package.json | jq '.workspaces'

# Verify package manager
echo -e "\nChecking package manager configuration..."
cat package.json | jq -r '.packageManager' 2>/dev/null || echo "No package manager specified"

Length of output: 399


Script:

#!/bin/bash
# Check the engine package's version in the workspace
echo "Checking @galacean/engine package version..."
cat packages/engine/package.json | jq '.version'

# Verify if other packages in the workspace also use the same dependency pattern
echo -e "\nChecking other packages using @galacean/engine..."
fd -e json -x cat {} \; -p packages/ | jq 'select(.dependencies."@galacean/engine" or .devDependencies."@galacean/engine" or .peerDependencies."@galacean/engine")'

Length of output: 4434


12-16: Verify the existence of entry point files

The package defines multiple entry points and type definitions. Let's ensure these paths are valid and the build system is configured to generate them.

✅ Verification successful

Let me gather more information about the build configuration and entry points.


Let me check the rollup configuration since we found a rollup.config.js file.


Based on the rollup configuration and file structure, I can now provide a final response:

Entry points configuration is properly set up

The rollup configuration correctly handles all the specified entry points in package.json:

  • dist/main.js (CommonJS) and dist/module.js (ES Module) are generated via the module() config
  • dist/browser.js is generated via the umd() config
  • TypeScript declaration files in types/ are handled by the TypeScript configuration with "declaration": true and "declarationDir": "types"
  • Source entry point src/index.ts exists at packages/ui/src/index.ts
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for build configuration that generates these files
echo "Checking build configuration..."
fd -t f "rollup|webpack|tsconfig" -x cat {} \; | grep -A 5 "dist/"

# Verify TypeScript configuration for types generation
echo -e "\nChecking TypeScript configuration..."
fd tsconfig.json -x cat {} \; | grep -A 5 "\"declaration\""

Length of output: 3364


Script:

#!/bin/bash
# Check for build configuration files
echo "Checking for build configuration files..."
fd -t f "rollup.config|webpack.config|vite.config"

# Check source entry point
echo -e "\nChecking source entry point..."
fd "index.ts" -p "src"

# Check if dist directory exists and its contents
echo -e "\nChecking dist directory contents..."
fd . -t f -p "dist"

# Check if types directory exists and its contents
echo -e "\nChecking types directory contents..."
fd . -t f -p "types"

Length of output: 753


Script:

#!/bin/bash
# Check rollup configuration
echo "Checking rollup configuration..."
cat rollup.config.js

# Check if the source entry point exists
echo -e "\nChecking for source files..."
fd -t f "index.ts" --full-path

# List all package.json files to understand project structure
echo -e "\nListing package.json files..."
fd -t f "package.json" --full-path

Length of output: 9114

packages/core/src/RenderPipeline/BasicRenderPipeline.ts (3)

343-343: LGTM! Good use of destructuring

The destructuring of camera properties improves both readability and performance by avoiding repeated property access.


Line range hint 376-382: LGTM! Well-structured bitwise flags enum

The RenderQueueFlags enum is well-designed:

  • Uses power-of-two values for efficient bitwise operations
  • Includes a comprehensive All flag
  • Values align with the RenderQueueType usage in pushRenderElementByType

364-373: Verify: Intentional omission of frustum culling for canvases

The code applies culling mask checks but not frustum culling for canvas elements. While this makes sense for UI elements that should always be visible, please verify this is intentional.

✅ Verification successful

Let me gather more information about canvas rendering by checking the implementation details.


Based on the search results, I can now provide a definitive response about the intentional omission of frustum culling for canvases.

Frustum culling behavior for canvases is correctly implemented

The code correctly handles frustum culling for canvases based on their render mode:

  • For ScreenSpaceOverlay mode: Frustum culling only checks if elements are within the screen bounds
  • For ScreenSpaceCamera and WorldSpace modes: Full frustum culling is applied using frustum.intersectsBox()

This is the expected behavior since overlay UI elements should only be culled based on screen bounds while world-space UI should respect the camera's view frustum.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for any existing frustum culling patterns with canvases
rg -A 5 "canvas.*frustum|frustum.*canvas" 

# Check if this is consistent with other UI/canvas related code
ast-grep --pattern 'class $_ implements Canvas {
  $$$
  _prepareRender($_) {
    $$$
  }
  $$$
}'

Length of output: 1621


Script:

#!/bin/bash
# Check Canvas interface definition and implementations
ast-grep --pattern 'interface Canvas {
  $$$
}'

# Look for canvas-related comments or documentation
rg -g '!*.{md,json}' -B 2 -A 2 'Canvas' --type ts

# Check if there are any frustum-related methods in canvas implementations
rg -g '!*.{md,json}' 'frustum' --type ts -B 2 -A 2

Length of output: 115384

packages/core/src/Engine.ts (3)

40-40: LGTM: Import addition aligns with new UI functionality

The addition of the UIUtils import is consistent with the new UI rendering capabilities introduced in this PR.


483-485: LGTM: UI update integration follows logical order

The UI update is correctly placed before the renderer update, ensuring UI state is consistent within the frame.


341-341: Verify removal of physics initialization check

The removal of the physicsInitialized check before firing pointer scripts could potentially cause issues if any pointer event handlers depend on physics being initialized. Please verify that this is intentional and that there are no pointer scripts that require physics initialization.

Let's check for any pointer scripts that might depend on physics:

Comment on lines +17 to +19
"scripts": {
"b:types": "tsc"
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider adding essential npm scripts

The scripts section only includes type generation. Consider adding standard lifecycle scripts.

Add these common npm scripts:

  "scripts": {
-   "b:types": "tsc"
+   "b:types": "tsc",
+   "build": "your-build-command",
+   "test": "your-test-command",
+   "clean": "rimraf dist types",
+   "prepare": "npm run build"
  },

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 129 to 130
? this.cureType._getZeroValue(this.crossEvaluateData.value)
: this.defaultValue;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Eliminate code duplication by extracting common logic.

This is the third occurrence of the same fallback value logic. Following the DRY principle, this is a strong indicator that we should extract this into a reusable method.

  1. Fix the indentation:
-        ? this.cureType._getZeroValue(this.crossEvaluateData.value)
-        : this.defaultValue;
+          ? this.cureType._getZeroValue(this.crossEvaluateData.value)
+          : this.defaultValue;
  1. Extract the common logic into a helper method as suggested earlier and use it in all three locations:
private _getFallbackValue(evaluateData: IEvaluateData<V>, additive: boolean): V {
  return additive 
    ? this.cureType._getZeroValue(evaluateData.value)
    : this.defaultValue;
}

This will improve maintainability and reduce the likelihood of inconsistencies when making future changes.

🧰 Tools
🪛 eslint

[error] 129-129: Insert ··

(prettier/prettier)


[error] 130-130: Insert ··

(prettier/prettier)

@github-actions github-actions bot added the documentation Improvements or additions to documentation label Dec 5, 2024
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (7)
docs/zh/basics/overview.md (1)

Line range hint 84-89: LGTM! Consider adding browser version requirements.

The compatibility table is well-structured and provides clear information about module-specific compatibility considerations along with solutions.

Consider enhancing the documentation by adding:

  • Minimum browser versions required for each module
  • A compatibility matrix showing which features work in which browsers/versions
docs/en/basics/overview.md (2)

84-84: Consider enhancing the compatibility table.

While the current table is informative, consider adding:

  1. Version requirements for each module (if applicable)
  2. Browser support matrix or minimum browser versions
  3. Links to fallback solutions or workarounds

Example addition:

| Module | Compatibility Considerations | Specific Documentation | Minimum Browser Versions |
| :-- | :-- | :-- | :-- |
| [Mouse and Touch](/en/docs/input) | [PointerEvent](https://caniuse.com/?search=PointerEvent) | For compatibility, please refer to [polyfill-pointer-event](https://github.com/galacean/polyfill-pointer-event) | Chrome 55+, Firefox 59+, Safari 13+ |
| [PhysX](/en/docs/physics/overall) | [WebAssembly](https://caniuse.com/?search=wasm) | In environments that do not support WebAssembly, it will degrade to the JS version, with slightly lower performance and performance compared to the WebAssembly version | Chrome 57+, Firefox 52+, Safari 11+ |

84-84: Fix typo in PhysX compatibility description.

There's a redundant word in the PhysX compatibility description.

-| [PhysX](/en/docs/physics/overall) | [WebAssembly](https://caniuse.com/?search=wasm) | In environments that do not support WebAssembly, it will degrade to the JS version, with slightly lower performance and performance compared to the WebAssembly version |
+| [PhysX](/en/docs/physics/overall) | [WebAssembly](https://caniuse.com/?search=wasm) | In environments that do not support WebAssembly, it will degrade to the JS version, with slightly lower performance compared to the WebAssembly version |
e2e/package.json (1)

4-4: Consider adding documentation for the major version bump

The version change from 1.3.20 to 1.4.0-alpha.0 represents a significant update that includes GUI-related features. Consider:

  • Adding a CHANGELOG.md entry
  • Providing migration guidelines if there are breaking changes
  • Documenting the new GUI features and their usage
packages/ui/src/component/advanced/Label.ts (1)

381-519: Consider optimizing text layout calculations.

The text layout calculation in _updateLocalData creates multiple temporary objects and performs repeated calculations. Consider these optimizations:

  1. Cache frequently accessed values
  2. Reuse Vector3 objects for calculations
  3. Use object pooling for temporary calculations

Example optimization for the position calculations:

+private static readonly _tempVec3 = new Vector3();
+private static readonly _tempBounds = {
+  minX: 0,
+  minY: 0,
+  maxX: 0,
+  maxY: 0
+};

 private _updateLocalData(): void {
   // ... existing code ...
-  let minX = Number.MAX_SAFE_INTEGER;
-  let minY = Number.MAX_SAFE_INTEGER;
-  let maxX = Number.MIN_SAFE_INTEGER;
-  let maxY = Number.MIN_SAFE_INTEGER;
+  const bounds = TextChunk._tempBounds;
+  bounds.minX = Number.MAX_SAFE_INTEGER;
+  bounds.minY = Number.MAX_SAFE_INTEGER;
+  bounds.maxX = Number.MIN_SAFE_INTEGER;
+  bounds.maxY = Number.MIN_SAFE_INTEGER;
   // ... use bounds object instead of individual variables
 }
🧰 Tools
🪛 Biome (1.9.4)

[error] 446-446: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 447-447: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 457-457: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 459-459: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/2d/text/TextRenderer.ts (1)

474-485: Consider caching pixel unit calculations.

The _pixelsPerUnit value is used multiple times in text measurements. Consider caching this value to avoid repeated access.

Apply this optimization:

 private _updateLocalData(): void {
   const { _pixelsPerUnit } = Engine;
+  const pixelsPerUnit = _pixelsPerUnit; // Cache the value
   const { min, max } = this._localBounds;
   const charRenderInfos = TextRenderer._charRenderInfos;
   const charFont = this._getSubFont();
   const textMetrics = this.enableWrapping
     ? TextUtils.measureTextWithWrap(
         this,
-        this.width * _pixelsPerUnit,
-        this.height * _pixelsPerUnit,
-        this._lineSpacing * _pixelsPerUnit
+        this.width * pixelsPerUnit,
+        this.height * pixelsPerUnit,
+        this._lineSpacing * pixelsPerUnit
       )
     : TextUtils.measureTextWithoutWrap(
         this,
-        this.height * _pixelsPerUnit,
-        this._lineSpacing * _pixelsPerUnit
+        this.height * pixelsPerUnit,
+        this._lineSpacing * pixelsPerUnit
       );
rollup.config.js (1)

188-188: Consider adding migration guide for existing users

The removal of mini program build support is a breaking change for existing Alipay platform users.

Consider:

  1. Adding a migration guide in the documentation
  2. Updating the changelog to clearly communicate this breaking change
  3. Bumping the major version number following semver
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between c4ace76 and 14008ad.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (30)
  • .gitignore (0 hunks)
  • README.md (1 hunks)
  • docs/en/_meta.json (0 hunks)
  • docs/en/assets/build.md (0 hunks)
  • docs/en/basics/overview.md (1 hunks)
  • docs/en/miniProgram/miniProgame.md (0 hunks)
  • docs/zh/_meta.json (0 hunks)
  • docs/zh/assets/build.md (0 hunks)
  • docs/zh/basics/overview.md (1 hunks)
  • docs/zh/miniProgram/miniProgame.md (0 hunks)
  • e2e/package.json (1 hunks)
  • package.json (1 hunks)
  • packages/core/package.json (1 hunks)
  • packages/core/src/2d/index.ts (1 hunks)
  • packages/core/src/2d/text/TextRenderer.ts (20 hunks)
  • packages/design/package.json (1 hunks)
  • packages/galacean/README.md (1 hunks)
  • packages/galacean/package.json (1 hunks)
  • packages/loader/package.json (1 hunks)
  • packages/math/package.json (1 hunks)
  • packages/physics-lite/package.json (1 hunks)
  • packages/physics-physx/package.json (1 hunks)
  • packages/rhi-webgl/package.json (1 hunks)
  • packages/shader-lab/package.json (1 hunks)
  • packages/ui/src/component/advanced/Label.ts (1 hunks)
  • packages/xr-webxr/package.json (1 hunks)
  • packages/xr/package.json (1 hunks)
  • rollup.config.js (1 hunks)
  • rollup.miniprogram.plugin.js (0 hunks)
  • tests/package.json (1 hunks)
💤 Files with no reviewable changes (8)
  • docs/en/_meta.json
  • rollup.miniprogram.plugin.js
  • docs/en/miniProgram/miniProgame.md
  • docs/zh/miniProgram/miniProgame.md
  • docs/zh/_meta.json
  • .gitignore
  • docs/en/assets/build.md
  • docs/zh/assets/build.md
✅ Files skipped from review due to trivial changes (13)
  • packages/design/package.json
  • tests/package.json
  • packages/loader/package.json
  • packages/math/package.json
  • packages/physics-lite/package.json
  • packages/core/package.json
  • packages/xr/package.json
  • packages/shader-lab/package.json
  • packages/physics-physx/package.json
  • packages/xr-webxr/package.json
  • packages/galacean/package.json
  • package.json
  • packages/rhi-webgl/package.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/core/src/2d/index.ts
🧰 Additional context used
🪛 Biome (1.9.4)
packages/ui/src/component/advanced/Label.ts

[error] 229-229: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 446-446: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 447-447: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 457-457: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 459-459: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 545-545: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 547-547: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

🔇 Additional comments (9)
docs/en/basics/overview.md (1)

84-84: LGTM! Well-structured compatibility information.

The added compatibility considerations section is well-organized and provides clear, actionable information about module-specific compatibility solutions.

packages/ui/src/component/advanced/Label.ts (3)

33-33: Fix array initialization.

The _textChunks array is incorrectly initialized. Using Array<TextChunk>() without the new keyword does not create a new array instance.

Apply this diff to fix the initialization:

-private _textChunks = Array<TextChunk>();
+private _textChunks: TextChunk[] = [];

58-183: LGTM! Well-structured property accessors.

The getters and setters are well-implemented with:

  • Proper null/undefined handling
  • Appropriate dirty flag management
  • Efficient value comparison before updates

541-589: LGTM! Efficient resource management.

The helper methods demonstrate good practices:

  • Efficient vertex data management in _buildChunk
  • Proper resource cleanup in _freeTextChunks
🧰 Tools
🪛 Biome (1.9.4)

[error] 545-545: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 547-547: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

packages/core/src/2d/text/TextRenderer.ts (2)

307-321: Add missing property declaration.

The _dirtyUpdateFlag property is used but not declared in the class.

Add the following declaration:

+private _dirtyUpdateFlag: number = 0;

695-705: LGTM! Well-structured update flags enum.

The TextRendererUpdateFlags enum is:

  • Well-documented with clear remarks
  • Properly structured with bit flags
  • Logically organized for different update scenarios
packages/galacean/README.md (1)

12-12: Verify platform support documentation consistency

The platform support change from Alipay to WeChat should be consistently reflected across all documentation and configuration files.

✅ Verification successful

Let me analyze the search results:

  1. The search shows that most of the matches are image URLs from alipayobjects.com domains, which are just image assets and not actual platform support references.

  2. Let's search specifically for platform support mentions in code and configuration files:


Let me analyze the search results:

  1. The codebase shows extensive platform-related code, but most of it is related to internal platform abstractions for rendering, textures, shaders, etc.

  2. Let's search specifically for WeChat/Alipay platform support in configuration files:


Let me analyze what we know:

  1. The original change updates the README.md to indicate WeChat minigame platform support instead of Alipay miniprogram.

  2. The codebase searches haven't revealed any configuration files or code that would need to be updated alongside this documentation change.

  3. Looking at the quick-start documentation in the codebase (from previous search), it mentions that WeChat platform support comes from a community contribution (deepkolos/platformize) rather than being built-in.

Based on this analysis, I can generate the final response:

Documentation change is accurate and self-contained

The platform support change in README.md from Alipay to WeChat minigame is accurate and doesn't require additional changes since:

  • WeChat support is provided through a community adaptation solution
  • No built-in platform configurations need to be updated
  • The change properly reflects the current platform support status
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for any remaining references to Alipay platform
rg -i "alipay|miniprogram" --type md

Length of output: 66588


Script:

#!/bin/bash
# Search for platform support mentions in code and configuration files
rg -i "platform|wechat|miniprogram|minigame" --type ts --type js --type json

Length of output: 37566


Script:

#!/bin/bash
# Search for platform configuration in package.json and other config files
rg -i "wechat|minigame|miniprogram|alipay" --type json

Length of output: 56

README.md (1)

14-14: LGTM! Platform support update is consistent

The platform support change matches the update in packages/galacean/README.md.

rollup.config.js (1)

188-188: Verify complete removal of mini program build configuration

The simplified getAll() function removes mini program builds. Please ensure:

  1. All mini program related imports and configurations are removed
  2. Documentation is updated to guide users migrating from Alipay to WeChat platform
✅ Verification successful

Based on the search results and previous findings, I can now generate the final response:

Mini program build configuration has been properly removed

The codebase cleanup appears complete:

  • No mini program build configurations found in rollup.config.js
  • No mini program related files exist in the repository
  • Only remaining references are comments in text rendering code that don't affect functionality
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for any remaining mini program related code
rg -i "mini(program|game)" --type js --type ts

# Check for any remaining build configuration files
fd -e js -e ts -g "*mini*"

Length of output: 392


Script:

#!/bin/bash
# Check the content of the files with remaining mini program references
cat packages/core/src/2d/text/index.ts | grep -A 5 -B 5 "miniprogram"
cat packages/core/src/2d/text/TextUtils.ts | grep -A 5 -B 5 "miniprogram"

# Search for any Alipay or WeChat platform specific code
rg -i "(alipay|wechat|weixin)" --type js --type ts

# Check rollup config for platform-specific builds
cat rollup.config.js

Length of output: 119510


Script:

#!/bin/bash
# Check rollup config for mini program related code
cat rollup.config.js | grep -A 5 -B 5 "mini"

# Check for any remaining mini program related files
find . -type f -name "*mini*" -not -path "./node_modules/*"

# Check package.json for mini program related dependencies
cat package.json | grep -i "mini"

Length of output: 1209

@@ -1,7 +1,7 @@
{
"name": "@galacean/engine-e2e",
"private": true,
"version": "1.3.20",
"version": "1.4.0-alpha.0",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Version mismatch detected in UI package

The version check reveals that all workspace packages are at version 1.4.0-alpha.0 except for @galacean/engine-ui which is at version 1.3.20. This inconsistency needs to be addressed.

  • ./packages/ui/package.json: Update version from 1.3.20 to 1.4.0-alpha.0 to maintain consistency with other workspace packages
🔗 Analysis chain

Verify coordinated version updates across workspace packages

The version bump to 1.4.0-alpha.0 should be synchronized across all referenced workspace packages to maintain consistency.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that all workspace packages are updated to the same version

# Check package.json files in workspace packages for version consistency
echo "Checking version consistency across workspace packages..."
for pkg in $(find . -name "package.json" -not -path "*/node_modules/*"); do
  if grep -q '"@galacean/engine' "$pkg"; then
    echo "Checking $pkg"
    grep '"version"' "$pkg"
  fi
done

Length of output: 4954

// @ts-ignore
this.setMaterial(engine._basicResources.textDefaultMaterial);
}
z;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove stray semicolon.

There appears to be a stray z; statement that serves no purpose.

Apply this diff to remove it:

-  z;

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (2)
packages/core/src/ui/UIUtils.ts (2)

22-23: Consider extracting matrix setup into a separate method

The matrix setup logic could be clearer if extracted into a dedicated method. This would improve readability and make the code more maintainable.

+  private static setupProjectionMatrix(projectE: Float32Array, canvas: HTMLCanvasElement): void {
+    projectE[0] = 2 / canvas.width;
+    projectE[5] = 2 / canvas.height;
+    projectE[10] = 0;
+  }

// In render method:
-    (projectE[0] = 2 / canvas.width), (projectE[5] = 2 / canvas.height), (projectE[10] = 0);
+    UIUtils.setupProjectionMatrix(projectE, canvas);

28-31: Extract camera transformation logic into a separate method

The camera transformation logic is complex and would benefit from being extracted into a dedicated method for better maintainability.

+  private static updateCameraTransform(
+    viewE: Float32Array,
+    transform: Transform,
+    virtualCamera: VirtualCamera,
+    renderContext: RenderContext
+  ): void {
+    viewE[12] = -transform.position.x;
+    viewE[13] = -transform.position.y;
+    Matrix.multiply(virtualCamera.projectionMatrix, virtualCamera.viewMatrix, virtualCamera.viewProjectionMatrix);
+    renderContext.applyVirtualCamera(virtualCamera, false);
+    renderContext.rendererUpdateFlag |= ContextRendererUpdateFlag.ProjectionMatrix;
+  }

// In render method:
-    (viewE[12] = -transform.position.x), (viewE[13] = -transform.position.y);
-    Matrix.multiply(virtualCamera.projectionMatrix, virtualCamera.viewMatrix, virtualCamera.viewProjectionMatrix);
-    renderContext.applyVirtualCamera(virtualCamera, false);
-    renderContext.rendererUpdateFlag |= ContextRendererUpdateFlag.ProjectionMatrix;
+    UIUtils.updateCameraTransform(viewE, transform, virtualCamera, renderContext);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 14008ad and c3a7089.

📒 Files selected for processing (1)
  • packages/core/src/ui/UIUtils.ts (1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
packages/core/src/ui/UIUtils.ts

[error] 10-40: Avoid classes that contain only static members.

Prefer using simple functions instead of classes with only static members.

(lint/complexity/noStaticOnlyClass)


[error] 16-16: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 16-16: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 17-17: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 17-17: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 18-18: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 18-18: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)

🔇 Additional comments (2)
packages/core/src/ui/UIUtils.ts (2)

10-14: Consider refactoring to a module with standalone functions

The UIUtils class is used as a namespace with only static members. For better maintainability and to align with modern TypeScript practices, consider converting it to a module with exported functions.

Additionally, initialize the static fields immediately to prevent potential race conditions:

-export class UIUtils {
-  private static _renderQueue: RenderQueue;
-  private static _virtualCamera: VirtualCamera;
-  private static _viewport: Vector4;
+let _renderQueue: RenderQueue = new RenderQueue(RenderQueueType.Transparent);
+let _virtualCamera: VirtualCamera = new VirtualCamera();
+let _viewport: Vector4 = new Vector4(0, 0, 1, 1);
+
+export function render(engine: Engine, uiCanvases: DisorderedArray<IUICanvas>): void {
🧰 Tools
🪛 Biome (1.9.4)

[error] 10-40: Avoid classes that contain only static members.

Prefer using simple functions instead of classes with only static members.

(lint/complexity/noStaticOnlyClass)


16-18: 🛠️ Refactor suggestion

Avoid assignments within expressions and cache Vector4 instance

The current implementation uses assignments within expressions and creates a new Vector4 instance every frame. This can be improved for better readability and performance.

-    const uiRenderQueue = (this._renderQueue ||= new RenderQueue(RenderQueueType.Transparent));
-    const virtualCamera = (this._virtualCamera ||= new VirtualCamera());
-    const viewport = (this._viewport ||= new Vector4(0, 0, 1, 1));
+    if (!UIUtils._renderQueue) {
+      UIUtils._renderQueue = new RenderQueue(RenderQueueType.Transparent);
+    }
+    if (!UIUtils._virtualCamera) {
+      UIUtils._virtualCamera = new VirtualCamera();
+    }
+    const uiRenderQueue = UIUtils._renderQueue;
+    const virtualCamera = UIUtils._virtualCamera;
+    const viewport = UIUtils._viewport;
🧰 Tools
🪛 Biome (1.9.4)

[error] 16-16: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 16-16: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 17-17: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 17-17: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)


[error] 18-18: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 18-18: Using this in a static context can be confusing.

this refers to the class.
Unsafe fix: Use the class name instead.

(lint/complexity/noThisInStatic)

Comment on lines +24 to +38
for (let i = 0, n = uiCanvases.length; i < n; i++) {
const uiCanvas = uiCanvases.get(i);
if (uiCanvas) {
const transform = uiCanvas.entity.transform;
(viewE[12] = -transform.position.x), (viewE[13] = -transform.position.y);
Matrix.multiply(virtualCamera.projectionMatrix, virtualCamera.viewMatrix, virtualCamera.viewProjectionMatrix);
renderContext.applyVirtualCamera(virtualCamera, false);
renderContext.rendererUpdateFlag |= ContextRendererUpdateFlag.ProjectionMatrix;
uiRenderQueue.clear();
uiCanvas._prepareRender(renderContext);
uiRenderQueue.pushRenderElement(uiCanvas._renderElement);
batcherManager.batch(uiRenderQueue);
uiRenderQueue.render(renderContext, "Forward");
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider adding error handling for render operations

The render loop should handle potential errors during rendering operations to prevent crashes and provide better debugging information.

     for (let i = 0, n = uiCanvases.length; i < n; i++) {
       const uiCanvas = uiCanvases.get(i);
       if (uiCanvas) {
+        try {
           const transform = uiCanvas.entity.transform;
           // ... rendering logic ...
           uiRenderQueue.render(renderContext, "Forward");
+        } catch (error) {
+          console.error(`Failed to render UI canvas at index ${i}:`, error);
+          // Optionally: continue rendering other canvases or rethrow
+        }
       }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for (let i = 0, n = uiCanvases.length; i < n; i++) {
const uiCanvas = uiCanvases.get(i);
if (uiCanvas) {
const transform = uiCanvas.entity.transform;
(viewE[12] = -transform.position.x), (viewE[13] = -transform.position.y);
Matrix.multiply(virtualCamera.projectionMatrix, virtualCamera.viewMatrix, virtualCamera.viewProjectionMatrix);
renderContext.applyVirtualCamera(virtualCamera, false);
renderContext.rendererUpdateFlag |= ContextRendererUpdateFlag.ProjectionMatrix;
uiRenderQueue.clear();
uiCanvas._prepareRender(renderContext);
uiRenderQueue.pushRenderElement(uiCanvas._renderElement);
batcherManager.batch(uiRenderQueue);
uiRenderQueue.render(renderContext, "Forward");
}
}
for (let i = 0, n = uiCanvases.length; i < n; i++) {
const uiCanvas = uiCanvases.get(i);
if (uiCanvas) {
try {
const transform = uiCanvas.entity.transform;
(viewE[12] = -transform.position.x), (viewE[13] = -transform.position.y);
Matrix.multiply(virtualCamera.projectionMatrix, virtualCamera.viewMatrix, virtualCamera.viewProjectionMatrix);
renderContext.applyVirtualCamera(virtualCamera, false);
renderContext.rendererUpdateFlag |= ContextRendererUpdateFlag.ProjectionMatrix;
uiRenderQueue.clear();
uiCanvas._prepareRender(renderContext);
uiRenderQueue.pushRenderElement(uiCanvas._renderElement);
batcherManager.batch(uiRenderQueue);
uiRenderQueue.render(renderContext, "Forward");
} catch (error) {
console.error(`Failed to render UI canvas at index ${i}:`, error);
// Optionally: continue rendering other canvases or rethrow
}
}
}

private static _virtualCamera: VirtualCamera;
private static _viewport: Vector4;

static render(engine: Engine, uiCanvases: DisorderedArray<IUICanvas>): void {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add input validation for method parameters

The render method should validate its input parameters to ensure robustness.

 static render(engine: Engine, uiCanvases: DisorderedArray<IUICanvas>): void {
+  if (!engine || !uiCanvases) {
+    throw new Error("Engine and uiCanvases parameters are required");
+  }

Committable suggestion skipped: line range outside the PR's diff.

@assignmentClone
private _ignoreParentGroup = false;
@ignoreClone
private _entityListeners: Entity[] = [];
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should delete, never used

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between c3a7089 and a10bb1b.

📒 Files selected for processing (1)
  • packages/ui/src/component/UICanvas.ts (1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
packages/ui/src/component/UICanvas.ts

[error] 159-159: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 222-222: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 237-237: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 437-437: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


[error] 406-408: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 587-588: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 597-598: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 599-601: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

const children = entity.children;
for (let i = 0, n = children.length; i < n; i++) {
const child = children[i];
child.isActive && (depth = this._walk(child, elements, depth));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid assignments within expressions to improve readability

Assigning a value within an expression can reduce code clarity. Consider refactoring to make the assignment explicit.

Apply the following diff:

- child.isActive && (depth = this._walk(child, elements, depth));
+ if (child.isActive) {
+   depth = this._walk(child, elements, depth);
+ }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
child.isActive && (depth = this._walk(child, elements, depth));
if (child.isActive) {
depth = this._walk(child, elements, depth);
}
🧰 Tools
🪛 Biome (1.9.4)

[error] 437-437: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

Comment on lines +587 to +588
case CanvasRenderMode.ScreenSpaceOverlay:
this._removeCanvasListener();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add a break statement to prevent unintended fall-through in switch case

In the case CanvasRenderMode.ScreenSpaceOverlay, missing a break statement causes a fall-through to the next case. Add a break to prevent unintended behavior.

Apply the following diff:

switch (preRealMode) {
  case CanvasRenderMode.ScreenSpaceOverlay:
    this._removeCanvasListener();
+   break;
  case CanvasRenderMode.ScreenSpaceCamera:
  case CanvasRenderMode.WorldSpace:
    componentsManager.removeUICanvas(this, preRealMode === CanvasRenderMode.ScreenSpaceOverlay);
    break;
  default:
    break;
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
case CanvasRenderMode.ScreenSpaceOverlay:
this._removeCanvasListener();
case CanvasRenderMode.ScreenSpaceOverlay:
this._removeCanvasListener();
break;
🧰 Tools
🪛 Biome (1.9.4)

[error] 587-588: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

Comment on lines +406 to +408
case ComponentType.UIRenderer:
elements[depth] = component as unknown as IGraphics;
++depth;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add a break statement to prevent unintended fall-through in switch case

The case ComponentType.UIRenderer lacks a break statement, causing an unintended fall-through to case ComponentType.UIInteractive. Add a break statement to prevent this.

Apply the following diff:

switch (componentType) {
  case ComponentType.UIRenderer:
    elements[depth] = component as unknown as IGraphics;
    ++depth;
+   break;
  case ComponentType.UIInteractive:
    if ((component as unknown as IGroupAble)._isGroupDirty) {
      tempGroupAbleList[groupAbleCount++] = component as unknown as IGroupAble;
    }
    if ((component as unknown as IElement)._isCanvasDirty) {
      Utils._registerElementToCanvas(component as unknown as IElement, null, this);
      Utils._registerElementToCanvasListener(component as unknown as IElement, this);
      (component as unknown as IElement)._isCanvasDirty = false;
    }
    break;
  case ComponentType.UIGroup:
    group = component as unknown as UIGroup;
    break;
  default:
    break;
}

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Biome (1.9.4)

[error] 406-408: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

const { enableFrustumCulling, cullingMask, _frustum: frustum } = context.camera;
const { frameCount } = engine.time;
// @ts-ignore
const renderElement = (this._renderElement = engine._renderElementPool.get());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid assignments within expressions to improve readability

Assigning a value within an expression can lead to confusion and potential bugs. Consider separating the assignment from the declaration.

Apply the following diff to refactor:

// @ts-ignore
this._renderElement = engine._renderElementPool.get();
const renderElement = this._renderElement;

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Biome (1.9.4)

[error] 222-222: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

Comment on lines +597 to +601
case CanvasRenderMode.ScreenSpaceOverlay:
this._addCanvasListener();
case CanvasRenderMode.ScreenSpaceCamera:
this._adapterPoseInScreenSpace();
this._adapterSizeInScreenSpace();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add break statements to prevent unintended fall-through in switch cases

The switch cases lack break statements, causing unintended fall-through. Add break statements after each case to ensure only the intended code executes.

Apply the following diff:

switch (curRealMode) {
  case CanvasRenderMode.ScreenSpaceOverlay:
    this._addCanvasListener();
+   break;
  case CanvasRenderMode.ScreenSpaceCamera:
    this._adapterPoseInScreenSpace();
    this._adapterSizeInScreenSpace();
+   break;
  case CanvasRenderMode.WorldSpace:
    componentsManager.addUICanvas(this, curRealMode === CanvasRenderMode.ScreenSpaceOverlay);
    break;
  default:
    break;
}

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Biome (1.9.4)

[error] 597-598: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 599-601: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)

Comment on lines +237 to +242
const { min, max } = renderer.bounds;
if (min.x > width || max.x < 0 || min.y > height || max.y < 0) {
continue;
}
break;
case CanvasRenderMode.ScreenSpaceCamera:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Wrap variable declarations in switch cases to limit scope

Declaring variables inside a case without a block can cause them to be accessible in subsequent cases due to JavaScript's hoisting. Wrap the case body in braces to restrict the scope of the variables.

Apply the following diff:

switch (mode) {
  case CanvasRenderMode.ScreenSpaceOverlay:
+   {
      const { min, max } = renderer.bounds;
      if (min.x > width || max.x < 0 || min.y > height || max.y < 0) {
        continue;
      }
      break;
+   }
  case CanvasRenderMode.ScreenSpaceCamera:
  case CanvasRenderMode.WorldSpace:
    if (!frustum.intersectsBox(renderer.bounds)) {
      continue;
    }
    break;
  default:
    break;
}

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Biome (1.9.4)

[error] 237-237: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

this._sortOrder = val;
this._realRenderMode === CanvasRenderMode.ScreenSpaceOverlay &&
// @ts-ignore
(this.scene._componentsManager._overlayCanvasesSortingFlag = true);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid assignments within expressions to improve readability

Assigning values within expressions can make the code harder to read and maintain. It's better to separate the assignment from the condition to enhance clarity.

Apply the following diff to refactor:

set sortOrder(val: number) {
  if (this._sortOrder !== val) {
    this._sortOrder = val;
    if (this._realRenderMode === CanvasRenderMode.ScreenSpaceOverlay) {
      // @ts-ignore
      this.scene._componentsManager._overlayCanvasesSortingFlag = true;
    }
  }
}

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Biome (1.9.4)

[error] 159-159: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation GUI
Projects
None yet
Development

Successfully merging this pull request may close these issues.

GUI 基建
7 participants